Repository: jhipster/jhipster-lite
Pull request #10879 (2024-09-16 21:16:04)

Generator: add the ability to snapshot test generated files in module tests
In some situation, it's interesting to check the whole generated file, rather than some small parts.
It can help detect unexpected changes.
It can also help testing the final result of the patching of a file.


Patch:
diff --git a/pom.xml b/pom.xml
index 32546d8a0..49f6b6e74 100644
--- a/pom.xml
+++ b/pom.xml
@@ -82,6 +82,7 @@
     <frontend-maven-plugin.version>1.15.0</frontend-maven-plugin.version>
     <checksum-maven-plugin.version>1.11</checksum-maven-plugin.version>
     <maven-antrun-plugin.version>3.1.0</maven-antrun-plugin.version>
+    <approvaltests.version>24.4.0</approvaltests.version>
   </properties>
 
   <dependencyManagement>
@@ -256,6 +257,12 @@
       <artifactId>mongodb</artifactId>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>com.approvaltests</groupId>
+      <artifactId>approvaltests</artifactId>
+      <version>${approvaltests.version}</version>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
 
   <build>


Test patch:
diff --git a/src/test/java/tech/jhipster/lite/PackageSettings.java b/src/test/java/tech/jhipster/lite/PackageSettings.java
new file mode 100644
index 000000000..5b4a46540
--- /dev/null
+++ b/src/test/java/tech/jhipster/lite/PackageSettings.java
@@ -0,0 +1,16 @@
+package tech.jhipster.lite;
+
+import org.approvaltests.core.ApprovalFailureReporter;
+import org.approvaltests.reporters.*;
+
+/**
+ * ApprovalTests.Java configuration.
+ * */
+public class PackageSettings {
+
+  private static final String ApprovalBaseDirectory = "../resources";
+  private static final ApprovalFailureReporter UseReporter = new FirstWorkingReporter(
+    new AutoApproveWhenEmptyReporter(),
+    new DiffReporter()
+  );
+}
diff --git a/src/test/java/tech/jhipster/lite/generator/client/vue/core/domain/VueModulesFactoryTest.java b/src/test/java/tech/jhipster/lite/generator/client/vue/core/domain/VueModulesFactoryTest.java
index 4aa78c54e..a0ba30e1c 100644
--- a/src/test/java/tech/jhipster/lite/generator/client/vue/core/domain/VueModulesFactoryTest.java
+++ b/src/test/java/tech/jhipster/lite/generator/client/vue/core/domain/VueModulesFactoryTest.java
@@ -62,46 +62,13 @@ class VueModulesFactoryTest {
       .and()
       .hasPrefixedFiles("", "eslint.config.js", "tsconfig.build.json", "vite.config.ts", "vitest.config.ts")
       .hasFile("tsconfig.json")
-        .containing("\"extends\": \"@vue/tsconfig/tsconfig.dom.json\"")
-        .containing("\"allowJs\": true,")
-        .containing("\"sourceMap\": true,")
-        .containing("\"types\": [\"vite/client\", ")
+        .matchingSavedSnapshot()
         .and()
       .hasFile("vitest.config.ts")
-        .containing("import vue from '@vitejs/plugin-vue';")
-        .containing("plugins: [vue(), tsconfigPaths()],")
-        .containing("environment: 'jsdom',")
-        .containing("""
-                exclude: [
-                  ...configDefaults.coverage.exclude as string[],
-                  'src/main/webapp/app/main.ts',
-                  'src/main/webapp/app/injections.ts',
-                  'src/main/webapp/app/router.ts',
-                  'src/main/webapp/**/*.component.ts',
-          """
-        )
+        .matchingSavedSnapshot()
         .and()
       .hasFile("eslint.config.js")
-        .containing("import vue from 'eslint-plugin-vue';")
-        .containing("""
-          ...vue.configs['flat/recommended'],
-          {
-            files: ['**/*.vue'],
-            languageOptions: {
-              parserOptions: { parser: '@typescript-eslint/parser' },
-              globals: { ...globals.browser },
-            },
-          },
-        """
-        )
-      .containing("""
-              rules: {
-                quotes: ['error', 'single', { avoidEscape: true }],
-                '@typescript-eslint/no-empty-object-type': 'off',
-                '@typescript-eslint/no-explicit-any': 'off',
-                'vue/html-self-closing': 'off',
-          """
-        )
+        .matchingSavedSnapshot()
       .and()
       .hasFiles("src/main/webapp/app/shared/http/infrastructure/secondary/AxiosHttp.ts")
       .hasFiles("src/main/webapp/index.html")
diff --git a/src/test/java/tech/jhipster/lite/module/infrastructure/secondary/FileSystemJHipsterModulesRepositoryTest.java b/src/test/java/tech/jhipster/lite/module/infrastructure/secondary/FileSystemJHipsterModulesRepositoryTest.java
index cf3d18c16..859dd6130 100644
--- a/src/test/java/tech/jhipster/lite/module/infrastructure/secondary/FileSystemJHipsterModulesRepositoryTest.java
+++ b/src/test/java/tech/jhipster/lite/module/infrastructure/secondary/FileSystemJHipsterModulesRepositoryTest.java
@@ -6,10 +6,7 @@ import static tech.jhipster.lite.module.infrastructure.secondary.JHipsterModules
 import ch.qos.logback.classic.Level;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
-import tech.jhipster.lite.Logs;
-import tech.jhipster.lite.LogsSpy;
-import tech.jhipster.lite.LogsSpyExtension;
-import tech.jhipster.lite.UnitTest;
+import tech.jhipster.lite.*;
 import tech.jhipster.lite.module.domain.JHipsterModule;
 
 @UnitTest
@@ -81,290 +78,10 @@ class FileSystemJHipsterModulesRepositoryTest {
       .containing("com.test.myapp")
       .and()
       .hasFile("pom.xml")
-      .containing("<spring-profiles-active>local</spring-profiles-active>")
-      .notContaining(
-        """
-              <dependency>
-                <groupId>net.logstash.logback</groupId>
-                <artifactId>logstash-logback-encoder</artifactId>
-              </dependency>
-          """
-      )
-      .notContaining(
-        """
-                <dependency>
-                  <groupId>org.springdoc</groupId>
-                  <artifactId>springdoc-openapi-ui</artifactId>
-                  <version>${springdoc-openapi.version}</version>
-                </dependency>
-          """
-      )
-      .containing("<reflections.version>")
-      .containing("</reflections.version>")
-      .containing(
-        """
-              <dependency>
-                <groupId>org.reflections</groupId>
-                <artifactId>reflections</artifactId>
-                <version>${reflections.version}</version>
-                <scope>test</scope>
-              </dependency>
-          """
-      )
-      .containing(
-        """
-                <dependency>
-                  <groupId>org.springframework.boot</groupId>
-                  <artifactId>spring-boot-dependencies</artifactId>
-                  <version>${spring-boot.version}</version>
-                  <scope>import</scope>
-                </dependency>
-          """
-      )
-      .containing(
-        """
-              <dependency>
-                <groupId>org.springframework.boot</groupId>
-                <artifactId>spring-boot-starter-web</artifactId>
-                <exclusions>
-                  <exclusion>
-                    <groupId>org.springframework.boot</groupId>
-                    <artifactId>spring-boot-starter-tomcat</artifactId>
-                  </exclusion>
-                </exclusions>
-              </dependency>
-          """
-      )
-      .containing(
-        """
-                <dependency>
-                  <groupId>org.springframework.boot</groupId>
-                  <artifactId>spring-boot-dependencies</artifactId>
-                  <version>${spring-boot.version}</version>
-                  <type>pom</type>
-                  <scope>import</scope>
-                </dependency>
-          """
-      )
-      .containing(
-        """
-              <dependency>
-                <groupId>org.junit.jupiter</groupId>
-                <artifactId>junit-jupiter-engine</artifactId>
-                <version>${spring-boot.version}</version>
-                <classifier>test</classifier>
-                <scope>test</scope>
-                <optional>true</optional>
-              </dependency>
-          """
-      )
-      .containing("<spring-boot.version>")
-      .containing("</spring-boot.version>")
-      .containing("<artifactId>spring-boot-starter</artifactId>")
-      .notContaining(
-        """
-              <dependency>
-                <groupId>org.assertj</groupId>
-                <artifactId>assertj-core</artifactId>
-                <version>${assertj.version}</version>
-                <scope>test</scope>
-              </dependency>
-          """
-      )
-      .containing("<maven-enforcer-plugin.version>")
-      .containing("<json-web-token.version>")
-      .containing(
-        """
-              <build>
-                <plugins>
-                  <plugin>
-                    <groupId>org.springframework.boot</groupId>
-                    <artifactId>spring-boot-maven-plugin</artifactId>
-                  </plugin>
-                  <plugin>
-                    <artifactId>maven-enforcer-plugin</artifactId>
-                  </plugin>
-                  <plugin>
-                    <groupId>org.asciidoctor</groupId>
-                    <artifactId>asciidoctor-maven-plugin</artifactId>
-                    <dependencies>
-                      <dependency>
-                        <groupId>org.asciidoctor</groupId>
-                        <artifactId>asciidoctorj-screenshot</artifactId>
-                      </dependency>
-                      <dependency>
-                        <groupId>org.asciidoctor</groupId>
-                        <artifactId>asciidoctorj-diagram</artifactId>
-                      </dependency>
-                    </dependencies>
-                  </plugin>
-                </plugins>
-                <pluginManagement>
-                  <plugins>
-                    <plugin>
-                      <groupId>org.springframework.boot</groupId>
-                      <artifactId>spring-boot-maven-plugin</artifactId>
-                      <version>${spring-boot.version}</version>
-                      <executions>
-                        <execution>
-                          <goals>
-                            <goal>repackage</goal>
-                          </goals>
-                        </execution>
-                      </executions>
-                      <configuration>
-                        <mainClass>${start-class}</mainClass>
-                      </configuration>
-                    </plugin>
-                    <plugin>
-                      <artifactId>maven-enforcer-plugin</artifactId>
-                      <version>${maven-enforcer-plugin.version}</version>
-                      <executions>
-                        <execution>
-                          <id>enforce-versions</id>
-                          <goals>
-                            <goal>enforce</goal>
-                          </goals>
-                        </execution>
-                        <execution>
-                          <id>enforce-dependencyConvergence</id>
-                          <goals>
-                            <goal>enforce</goal>
-                          </goals>
-                          <configuration>
-                            <rules>
-                              <DependencyConvergence />
-                            </rules>
-                            <fail>false</fail>
-                          </configuration>
-                        </execution>
-                      </executions>
-                      <dependencies>
-                        <dependency>
-                          <groupId>io.jsonwebtoken</groupId>
-                          <artifactId>jjwt-jackson</artifactId>
-                          <version>${json-web-token.version}</version>
-                          <exclusions>
-                            <exclusion>
-                              <groupId>com.fasterxml.jackson.core</groupId>
-                              <artifactId>jackson-databind</artifactId>
-                            </exclusion>
-                          </exclusions>
-                        </dependency>
-                      </dependencies>
-                      <configuration>
-                        <rules>
-                          <requireMavenVersion>
-                            <message>You are running an older version of Maven. JHipster requires at least Maven ${maven.version}</message>
-                            <version>[${maven.version},)</version>
-                          </requireMavenVersion>
-                          <requireJavaVersion>
-                            <message>You are running an incompatible version of Java. JHipster engine supports JDK 21+.</message>
-                            <version>[21,22)</version>
-                          </requireJavaVersion>
-                        </rules>
-                      </configuration>
-                    </plugin>
-                  </plugins>
-                </pluginManagement>
-              </build>
-            """
-      )
-      .containing("<cassandraunit.version>")
-      .containing(
-        """
-              <profiles>
-                <profile>
-                  <id>local</id>
-                  <activation />
-                  <build>
-                    <pluginManagement>
-                      <plugins>
-                        <plugin>
-                          <artifactId>maven-enforcer-plugin</artifactId>
-                          <version>${maven-enforcer-plugin.version}</version>
-                          <executions>
-                            <execution>
-                              <id>enforce-versions</id>
-                              <goals>
-                                <goal>enforce</goal>
-                              </goals>
-                            </execution>
-                            <execution>
-                              <id>enforce-dependencyConvergence</id>
-                              <goals>
-                                <goal>enforce</goal>
-                              </goals>
-                              <configuration>
-                                <rules>
-                                  <DependencyConvergence />
-                                </rules>
-                                <fail>false</fail>
-                              </configuration>
-                            </execution>
-                          </executions>
-                          <dependencies>
-                            <dependency>
-                              <groupId>io.jsonwebtoken</groupId>
-                              <artifactId>jjwt-jackson</artifactId>
-                              <version>${json-web-token.version}</version>
-                              <exclusions>
-                                <exclusion>
-                                  <groupId>com.fasterxml.jackson.core</groupId>
-                                  <artifactId>jackson-databind</artifactId>
-                                </exclusion>
-                              </exclusions>
-                            </dependency>
-                          </dependencies>
-                          <configuration>
-                            <rules>
-                              <requireMavenVersion>
-                                <message>You are running an older version of Maven. JHipster requires at least Maven ${maven.version}</message>
-                                <version>[${maven.version},)</version>
-                              </requireMavenVersion>
-                              <requireJavaVersion>
-                                <message>You are running an incompatible version of Java. JHipster engine supports JDK 21+.</message>
-                                <version>[21,22)</version>
-                              </requireJavaVersion>
-                            </rules>
-                          </configuration>
-                        </plugin>
-                      </plugins>
-                    </pluginManagement>
-                    <plugins>
-                      <plugin>
-                        <artifactId>maven-enforcer-plugin</artifactId>
-                      </plugin>
-                    </plugins>
-                  </build>
-                  <properties>
-                    <spring.profiles.active>local</spring.profiles.active>
-                  </properties>
-                  <dependencies>
-                    <dependency>
-                      <groupId>org.cassandraunit</groupId>
-                      <artifactId>cassandra-unit</artifactId>
-                      <version>${cassandraunit.version}</version>
-                      <scope>test</scope>
-                    </dependency>
-                  </dependencies>
-                </profile>
-              </profiles>
-            """
-      )
+      .matchingSavedSnapshot()
       .and()
       .hasFile("package.json")
-      .containing("  \"jestSonar\": {\n    \"reportPath\": \"target/test-results\",\n    \"reportFile\": \"TESTS-results-sonar.xml\"\n  }")
-      .containing(
-        """
-          "scripts": {
-            "build": "ng build --output-path=target/classes/static",
-            "serve": "tikui-core serve"
-          },
-        """
-      )
-      .containing("\"dependencies\": {\n    \"@angular/animations\": \"")
-      .containing("\"devDependencies\": {\n    \"@playwright/test\": \"")
+      .matchingSavedSnapshot()
       .and()
       .hasFile("src/main/java/com/company/myapp/errors/Assert.java")
       .containing("Dummy replacement")
@@ -470,81 +187,10 @@ class FileSystemJHipsterModulesRepositoryTest {
         .containing("com.test.myapp")
         .and()
       .hasFile("gradle/libs.versions.toml")
-        .containing("spring-boot = \"")
-        .containing("json-web-token = \"")
-        .containing("cassandraunit = \"")
-        .containing("git-properties = \"")
+        .matchingSavedSnapshot()
         .and()
       .hasFile("build.gradle.kts")
-        .notContaining("implementation(libs.logstash.logback.encoder)")
-        .notContaining("implementation(libs.springdoc.openapi.ui)")
-        .containing("implementation(platform(libs.spring.boot.dependencies))")
-        .containing("implementation(libs.spring.boot.starter.web)")
-        .containing("testImplementation(libs.junit.jupiter.engine)")
-        .containing("implementation(libs.spring.boot.starter)")
-        .notContaining("testImplementation(libs.assertj.core)")
-        .containing(
-          """
-          import java.util.Properties
-          // jhipster-needle-gradle-imports
-          """
-        )
-        .containing("""
-          plugins {
-            java
-            jacoco
-            checkstyle
-            // jhipster-needle-gradle-plugins
-          }
-          """
-        )
-        .containing("""
-          checkstyle {
-            toolVersion = libs.versions.checkstyle.get()
-          }
-          """
-        )
-        .containing(
-          """
-          val profiles = (project.findProperty("profiles") as String? ?: "")
-            .split(",")
-            .map { it.trim() }
-            .filter { it.isNotEmpty() }
-          if (profiles.contains("local")) {
-            apply(plugin = "profile-local")
-          }
-          // jhipster-needle-profile-activation\
-          """
-        )
-        .containing(
-          """
-          val springProfilesActive by extra("local")
-          // jhipster-needle-gradle-properties
-          """
-        )
-        .containing(
-          """
-          tasks.build {
-            dependsOn("processResources")
-          }
-
-          tasks.processResources {
-            filesMatching("**/*.yml", "**/*.properties") {
-              filter {
-                it.replace("@spring.profiles.active@", springProfilesActive)
-              }
-            }
-          }
-
-          // jhipster-needle-gradle-free-configuration-blocks
-          """
-        )
-        .containing(
-          """
-            finalizedBy("jacocoTestReport")
-            // jhipster-needle-gradle-tasks-test
-          """
-        )
+        .matchingSavedSnapshot()
         .and()
       .hasFile("buildSrc/build.gradle.kts")
         .containing(
@@ -603,17 +249,7 @@ class FileSystemJHipsterModulesRepositoryTest {
         )
         .and()
       .hasFile("package.json")
-        .containing("  \"jestSonar\": {\n    \"reportPath\": \"build/test-results\",\n    \"reportFile\": \"TESTS-results-sonar.xml\"\n  }")
-        .containing(
-          """
-            "scripts": {
-              "build": "ng build --output-path=build/classes/static",
-              "serve": "tikui-core serve"
-            },
-          """
-        )
-        .containing("\"dependencies\": {\n    \"@angular/animations\": \"")
-        .containing("\"devDependencies\": {\n    \"@playwright/test\": \"")
+        .matchingSavedSnapshot()
         .and()
       .hasFile("src/main/java/com/company/myapp/errors/Assert.java")
         .containing("Dummy replacement")
diff --git a/src/test/java/tech/jhipster/lite/module/infrastructure/secondary/JHipsterModulesAssertions.java b/src/test/java/tech/jhipster/lite/module/infrastructure/secondary/JHipsterModulesAssertions.java
index cb33baf5c..1395fcde1 100644
--- a/src/test/java/tech/jhipster/lite/module/infrastructure/secondary/JHipsterModulesAssertions.java
+++ b/src/test/java/tech/jhipster/lite/module/infrastructure/secondary/JHipsterModulesAssertions.java
@@ -12,6 +12,10 @@ import java.util.function.Consumer;
 import java.util.function.Supplier;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
+import org.approvaltests.Approvals;
+import org.approvaltests.core.Scrubber;
+import org.approvaltests.scrubbers.NoOpScrubber;
+import org.approvaltests.scrubbers.RegExScrubber;
 import org.assertj.core.api.SoftAssertions;
 import tech.jhipster.lite.module.domain.JHipsterModule;
 import tech.jhipster.lite.module.domain.JHipsterModuleUpgrade;
@@ -332,6 +336,40 @@ public final class JHipsterModulesAssertions {
       assertThat(Files.exists(path)).as(fileNotFoundMessage(path, projectFolder)).isTrue();
     }
 
+    /**
+     * Verifies that the file content matches the saved snapshot, using ApprovalTests.
+     */
+    public JHipsterModuleFileAsserter<T> matchingSavedSnapshot() {
+      String shortFileName = Arrays.stream(file.split("/")).toList().getLast();
+      Approvals.verify(
+        contentNormalizingNewLines(projectFolder.filePath(file)),
+        Approvals.NAMES.withParameters(shortFileName).withScrubber(scrubberFor(file))
+      );
+
+      return this;
+    }
+
+    private Scrubber scrubberFor(String file) {
+      return switch (file) {
+        case "pom.xml" -> mavenVersionScrubber();
+        case "package.json" -> npmVersionScrubber();
+        case "gradle/libs.versions.toml" -> gradleCatalogVersionScrubber();
+        default -> NoOpScrubber.INSTANCE;
+      };
+    }
+
+    private Scrubber npmVersionScrubber() {
+      return new RegExScrubber("\": \"[^~]?(\\d+\\.?)+\"", "\": \"[version]\"");
+    }
+
+    private Scrubber mavenVersionScrubber() {
+      return new RegExScrubber(">(\\d+\\.?)+</", ">[version]</");
+    }
+
+    private Scrubber gradleCatalogVersionScrubber() {
+      return new RegExScrubber(" = \"(\\d+\\.?)+\"", " = \"[version]\"");
+    }
+
     public JHipsterModuleFileAsserter<T> containing(String content) {
       assertThat(content).as("Can't check blank content").isNotBlank();
 
diff --git a/src/test/resources/tech/jhipster/lite/generator/client/vue/core/domain/VueModulesFactoryTest.shouldCreateVueModule.eslint.config.js.approved.txt b/src/test/resources/tech/jhipster/lite/generator/client/vue/core/domain/VueModulesFactoryTest.shouldCreateVueModule.eslint.config.js.approved.txt
new file mode 100644
index 000000000..9608b0fe0
--- /dev/null
+++ b/src/test/resources/tech/jhipster/lite/generator/client/vue/core/domain/VueModulesFactoryTest.shouldCreateVueModule.eslint.config.js.approved.txt
@@ -0,0 +1,39 @@
+import js from '@eslint/js';
+import globals from 'globals';
+import typescript from 'typescript-eslint';
+import vue from 'eslint-plugin-vue';
+
+export default typescript.config(
+  {
+    languageOptions: {
+      globals: {
+        ...globals.node,
+      },
+    },
+  },
+  {
+    ignores: ['target/'],
+  },
+  js.configs.recommended,
+  ...typescript.configs.recommended.map(config => (config.name === 'typescript-eslint/base' ? config : { ...config, files: ['**/*.ts'] })),
+  ...vue.configs['flat/recommended'],
+  {
+    files: ['**/*.vue'],
+    languageOptions: {
+      parserOptions: { parser: '@typescript-eslint/parser' },
+      globals: { ...globals.browser },
+    },
+  },
+  {
+    files: ['src/*/webapp/**/*.vue', 'src/*/webapp/**/*.ts'],
+    languageOptions: {
+      globals: { ...globals.browser },
+    },
+    rules: {
+      quotes: ['error', 'single', { avoidEscape: true }],
+      '@typescript-eslint/no-empty-object-type': 'off',
+      '@typescript-eslint/no-explicit-any': 'off',
+      'vue/html-self-closing': 'off',
+    },
+  },
+);
diff --git a/src/test/resources/tech/jhipster/lite/generator/client/vue/core/domain/VueModulesFactoryTest.shouldCreateVueModule.tsconfig.json.approved.txt b/src/test/resources/tech/jhipster/lite/generator/client/vue/core/domain/VueModulesFactoryTest.shouldCreateVueModule.tsconfig.json.approved.txt
new file mode 100644
index 000000000..6bef65ee0
--- /dev/null
+++ b/src/test/resources/tech/jhipster/lite/generator/client/vue/core/domain/VueModulesFactoryTest.shouldCreateVueModule.tsconfig.json.approved.txt
@@ -0,0 +1,13 @@
+{
+  "extends": "@vue/tsconfig/tsconfig.dom.json",
+  "compilerOptions": {
+    "allowJs": true,
+    "sourceMap": true,
+    "types": ["vite/client", "vitest/globals"],
+    "baseUrl": ".",
+    "paths": {
+      "@/*": ["src/main/webapp/app/*"]
+    }
+  },
+  "include": ["src/main/webapp/**/*", "src/test/webapp/unit/**/*"]
+}
diff --git a/src/test/resources/tech/jhipster/lite/generator/client/vue/core/domain/VueModulesFactoryTest.shouldCreateVueModule.vitest.config.ts.approved.txt b/src/test/resources/tech/jhipster/lite/generator/client/vue/core/domain/VueModulesFactoryTest.shouldCreateVueModule.vitest.config.ts.approved.txt
new file mode 100644
index 000000000..d66cc33dc
--- /dev/null
+++ b/src/test/resources/tech/jhipster/lite/generator/client/vue/core/domain/VueModulesFactoryTest.shouldCreateVueModule.vitest.config.ts.approved.txt
@@ -0,0 +1,50 @@
+/// <reference types="vitest" />
+
+import tsconfigPaths from 'vite-tsconfig-paths';
+import { defineConfig, configDefaults } from 'vitest/config';
+import vue from '@vitejs/plugin-vue';
+
+export default defineConfig({
+  plugins: [vue(), tsconfigPaths()],
+  test: {
+    reporters: ['verbose', 'vitest-sonar-reporter'],
+    outputFile: {
+      'vitest-sonar-reporter': 'target/test-results/TESTS-results-sonar.xml',
+    },
+    globals: true,
+    logHeapUsage: true,
+    poolOptions: {
+      threads: {
+        minThreads: 1,
+        maxThreads: 2,
+      },
+    },
+    environment: 'jsdom',
+    cache: false,
+    include: ['src/test/webapp/unit/**/*.{test,spec}.?(c|m)[jt]s?(x)'],
+    coverage: {
+      thresholds: {
+        perFile: true,
+        autoUpdate: true,
+        100: true,
+      },
+      include: ['src/main/webapp/**/*.ts?(x)'],
+      exclude: [
+        ...configDefaults.coverage.exclude as string[],
+        'src/main/webapp/app/main.ts',
+        'src/main/webapp/app/injections.ts',
+        'src/main/webapp/app/router.ts',
+        'src/main/webapp/**/*.component.ts',
+      ],
+      provider: 'istanbul',
+      reportsDirectory: 'target/test-results/',
+      reporter: ['html', 'json-summary', 'text', 'text-summary', 'lcov', 'clover'],
+      watermarks: {
+        statements: [100, 100],
+        branches: [100, 100],
+        functions: [100, 100],
+        lines: [100, 100],
+      },
+    },
+  },
+});
diff --git a/src/test/resources/tech/jhipster/lite/module/infrastructure/secondary/FileSystemJHipsterModulesRepositoryTest.shouldApplyModuleToGradleProject.build.gradle.kts.approved.txt b/src/test/resources/tech/jhipster/lite/module/infrastructure/secondary/FileSystemJHipsterModulesRepositoryTest.shouldApplyModuleToGradleProject.build.gradle.kts.approved.txt
new file mode 100644
index 000000000..ee3b40e79
--- /dev/null
+++ b/src/test/resources/tech/jhipster/lite/module/infrastructure/secondary/FileSystemJHipsterModulesRepositoryTest.shouldApplyModuleToGradleProject.build.gradle.kts.approved.txt
@@ -0,0 +1,109 @@
+import java.util.Properties
+// jhipster-needle-gradle-imports
+
+plugins {
+  java
+  jacoco
+  checkstyle
+  // jhipster-needle-gradle-plugins
+}
+
+val springProfilesActive by extra("local")
+// jhipster-needle-gradle-properties
+
+java {
+  toolchain {
+    languageVersion = JavaLanguageVersion.of(21)
+  }
+}
+
+
+jacoco {
+  toolVersion = libs.versions.jacoco.get()
+}
+
+tasks.jacocoTestReport {
+  dependsOn("test", "integrationTest")
+  reports {
+    xml.required.set(true)
+    html.required.set(false)
+  }
+  executionData.setFrom(fileTree(buildDir).include("**/jacoco/test.exec", "**/jacoco/integrationTest.exec"))
+}
+
+
+checkstyle {
+  toolVersion = libs.versions.checkstyle.get()
+}
+
+// jhipster-needle-gradle-plugins-configurations
+
+repositories {
+  mavenCentral()
+  // jhipster-needle-gradle-repositories
+}
+
+group = "tech.jhipster.jhlitest"
+version = "0.0.1-SNAPSHOT"
+
+val profiles = (project.findProperty("profiles") as String? ?: "")
+  .split(",")
+  .map { it.trim() }
+  .filter { it.isNotEmpty() }
+if (profiles.contains("local")) {
+  apply(plugin = "profile-local")
+}
+// jhipster-needle-profile-activation
+
+dependencies {
+  implementation(platform(libs.spring.boot.dependencies))
+  implementation(libs.spring.boot.starter)
+  implementation(libs.jjwt.api)
+  implementation(libs.spring.boot.starter.web) {
+    exclude(group = "org.springframework.boot", module = "spring-boot-starter-tomcat")
+  }
+  // jhipster-needle-gradle-implementation-dependencies
+  // jhipster-needle-gradle-compile-dependencies
+  // jhipster-needle-gradle-runtime-dependencies
+  testImplementation(libs.commons.lang3)
+  testImplementation(libs.reflections)
+  testImplementation(libs.junit.jupiter.engine)
+  // jhipster-needle-gradle-test-dependencies
+}
+
+
+tasks.build {
+  dependsOn("processResources")
+}
+
+tasks.processResources {
+  filesMatching("**/*.yml", "**/*.properties") {
+    filter {
+      it.replace("@spring.profiles.active@", springProfilesActive)
+    }
+  }
+}
+
+// jhipster-needle-gradle-free-configuration-blocks
+
+tasks.test {
+  filter {
+    includeTestsMatching("**Test*")
+    excludeTestsMatching("**IT*")
+    excludeTestsMatching("**CucumberTest*")
+  }
+  useJUnitPlatform()
+  finalizedBy("jacocoTestReport")
+  // jhipster-needle-gradle-tasks-test
+}
+
+val integrationTest = task<Test>("integrationTest") {
+  description = "Runs integration tests."
+  group = "verification"
+  shouldRunAfter("test")
+  filter {
+    includeTestsMatching("**IT*")
+    includeTestsMatching("**CucumberTest*")
+  }
+  useJUnitPlatform()
+}
diff --git a/src/test/resources/tech/jhipster/lite/module/infrastructure/secondary/FileSystemJHipsterModulesRepositoryTest.shouldApplyModuleToGradleProject.libs.versions.toml.approved.txt b/src/test/resources/tech/jhipster/lite/module/infrastructure/secondary/FileSystemJHipsterModulesRepositoryTest.shouldApplyModuleToGradleProject.libs.versions.toml.approved.txt
new file mode 100644
index 000000000..4b30db601
--- /dev/null
+++ b/src/test/resources/tech/jhipster/lite/module/infrastructure/secondary/FileSystemJHipsterModulesRepositoryTest.shouldApplyModuleToGradleProject.libs.versions.toml.approved.txt
@@ -0,0 +1,69 @@
+plugins = {}
+bundles = {}
+
+[versions]
+	json-web-token = "[version]"
+	reflections = "[version]"
+	cassandraunit = "[version]"
+	git-properties = "[version]"
+	jacoco = "[version]"
+	spring-boot = "[version]"
+	commons-lang3 = "[version]"
+	checkstyle = "[version]"
+
+	[libraries.jjwt-api]
+		name = "jjwt-api"
+		group = "io.jsonwebtoken"
+
+		[libraries.jjwt-api.version]
+			ref = "json-web-token"
+
+	[libraries.junit-jupiter-engine]
+		name = "junit-jupiter-engine"
+		group = "org.junit.jupiter"
+
+		[libraries.junit-jupiter-engine.version]
+			ref = "spring-boot"
+
+	[libraries.spring-boot-starter]
+		name = "spring-boot-starter"
+		group = "org.springframework.boot"
+
+	[libraries.spring-boot-starter-web]
+		name = "spring-boot-starter-web"
+		group = "org.springframework.boot"
+
+	[libraries.reflections]
+		name = "reflections"
+		group = "org.reflections"
+
+		[libraries.reflections.version]
+			ref = "reflections"
+
+	[libraries.spring-boot-dependencies]
+		name = "spring-boot-dependencies"
+		group = "org.springframework.boot"
+
+		[libraries.spring-boot-dependencies.version]
+			ref = "spring-boot"
+
+	[libraries.commons-lang3]
+		name = "commons-lang3"
+		group = "org.apache.commons"
+
+		[libraries.commons-lang3.version]
+			ref = "commons-lang3"
+
+	[libraries.cassandra-unit]
+		name = "cassandra-unit"
+		group = "org.cassandraunit"
+
+		[libraries.cassandra-unit.version]
+			ref = "cassandraunit"
+
+	[libraries.gradle-git-properties]
+		name = "gradle-git-properties"
+		group = "com.gorylenko.gradle-git-properties"
+
+		[libraries.gradle-git-properties.version]
+			ref = "git-properties"
diff --git a/src/test/resources/tech/jhipster/lite/module/infrastructure/secondary/FileSystemJHipsterModulesRepositoryTest.shouldApplyModuleToGradleProject.package.json.approved.txt b/src/test/resources/tech/jhipster/lite/module/infrastructure/secondary/FileSystemJHipsterModulesRepositoryTest.shouldApplyModuleToGradleProject.package.json.approved.txt
new file mode 100644
index 000000000..f0984d205
--- /dev/null
+++ b/src/test/resources/tech/jhipster/lite/module/infrastructure/secondary/FileSystemJHipsterModulesRepositoryTest.shouldApplyModuleToGradleProject.package.json.approved.txt
@@ -0,0 +1,25 @@
+{
+  "name": "test-jhipster-project",
+  "version": "[version]",
+  "private": true,
+  "description": "JHipster Project",
+  "license": "UNLICENSED",
+  "jestSonar": {
+    "reportPath": "build/test-results",
+    "reportFile": "TESTS-results-sonar.xml"
+  },
+
+  "engines": {
+    "node": ">=16.13.0"
+  },
+  "scripts": {
+    "build": "ng build --output-path=build/classes/static",
+    "serve": "tikui-core serve"
+  },
+  "devDependencies": {
+    "@playwright/test": "[version]"
+  },
+  "dependencies": {
+    "@angular/animations": "[version]"
+  }
+}
diff --git a/src/test/resources/tech/jhipster/lite/module/infrastructure/secondary/FileSystemJHipsterModulesRepositoryTest.shouldApplyModuleToMavenProject.package.json.approved.txt b/src/test/resources/tech/jhipster/lite/module/infrastructure/secondary/FileSystemJHipsterModulesRepositoryTest.shouldApplyModuleToMavenProject.package.json.approved.txt
new file mode 100644
index 000000000..02545080c
--- /dev/null
+++ b/src/test/resources/tech/jhipster/lite/module/infrastructure/secondary/FileSystemJHipsterModulesRepositoryTest.shouldApplyModuleToMavenProject.package.json.approved.txt
@@ -0,0 +1,25 @@
+{
+  "name": "test-jhipster-project",
+  "version": "[version]",
+  "private": true,
+  "description": "JHipster Project",
+  "license": "UNLICENSED",
+  "jestSonar": {
+    "reportPath": "target/test-results",
+    "reportFile": "TESTS-results-sonar.xml"
+  },
+
+  "engines": {
+    "node": ">=16.13.0"
+  },
+  "scripts": {
+    "build": "ng build --output-path=target/classes/static",
+    "serve": "tikui-core serve"
+  },
+  "devDependencies": {
+    "@playwright/test": "[version]"
+  },
+  "dependencies": {
+    "@angular/animations": "[version]"
+  }
+}
diff --git a/src/test/resources/tech/jhipster/lite/module/infrastructure/secondary/FileSystemJHipsterModulesRepositoryTest.shouldApplyModuleToMavenProject.pom.xml.approved.txt b/src/test/resources/tech/jhipster/lite/module/infrastructure/secondary/FileSystemJHipsterModulesRepositoryTest.shouldApplyModuleToMavenProject.pom.xml.approved.txt
new file mode 100644
index 000000000..197f8bae4
--- /dev/null
+++ b/src/test/resources/tech/jhipster/lite/module/infrastructure/secondary/FileSystemJHipsterModulesRepositoryTest.shouldApplyModuleToMavenProject.pom.xml.approved.txt
@@ -0,0 +1,259 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>[version]</modelVersion>
+
+  <artifactId>jhlite-test-maven-project</artifactId>
+  <version>[version]</version>
+  <name>Test project</name>
+  <packaging>pom</packaging>
+
+  <properties>
+    <json-web-token.version>[version]</json-web-token.version>
+    <logstash-logback-encoder.version>[version]</logstash-logback-encoder.version>
+    <spring-boot.version>[version]</spring-boot.version>
+    <commons-lang3.version>[version]</commons-lang3.version>
+    <reflections.version>[version]</reflections.version>
+    <maven-enforcer-plugin.version>[version]</maven-enforcer-plugin.version>
+    <spring-profiles-active>local</spring-profiles-active>
+    <cassandraunit.version>[version]</cassandraunit.version>
+  </properties>
+
+  <dependencyManagement>
+    <dependencies>
+      <dependency>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-dependencies</artifactId>
+        <version>${spring-boot.version}</version>
+        <type>pom</type>
+        <scope>import</scope>
+      </dependency>
+      <dependency>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-dependencies</artifactId>
+        <version>${spring-boot.version}</version>
+        <scope>import</scope>
+      </dependency>
+      <dependency>
+        <groupId>org.apache.commons</groupId>
+        <artifactId>commons-lang3</artifactId>
+        <version>${commons-lang3.version}</version>
+        <scope>test</scope>
+      </dependency>
+    </dependencies>
+  </dependencyManagement>
+
+  <dependencies>
+    <dependency>
+      <groupId>io.jsonwebtoken</groupId>
+      <artifactId>jjwt-api</artifactId>
+      <version>${json-web-token.version}</version>
+      <classifier>classif</classifier>
+      <scope>test</scope>
+      <optional>true</optional>
+    </dependency>
+    <dependency>
+      <groupId>org.reflections</groupId>
+      <artifactId>reflections</artifactId>
+      <version>${reflections.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.springframework.boot</groupId>
+      <artifactId>spring-boot-starter</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>io.jsonwebtoken</groupId>
+      <artifactId>jjwt-api</artifactId>
+      <version>${json-web-token.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.springframework.boot</groupId>
+      <artifactId>spring-boot-starter-web</artifactId>
+      <exclusions>
+        <exclusion>
+          <groupId>org.springframework.boot</groupId>
+          <artifactId>spring-boot-starter-tomcat</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+    <dependency>
+      <groupId>org.junit.jupiter</groupId>
+      <artifactId>junit-jupiter-engine</artifactId>
+      <version>${spring-boot.version}</version>
+      <classifier>test</classifier>
+      <scope>test</scope>
+      <optional>true</optional>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-maven-plugin</artifactId>
+      </plugin>
+      <plugin>
+        <artifactId>maven-enforcer-plugin</artifactId>
+      </plugin>
+      <plugin>
+        <groupId>org.asciidoctor</groupId>
+        <artifactId>asciidoctor-maven-plugin</artifactId>
+        <dependencies>
+          <dependency>
+            <groupId>org.asciidoctor</groupId>
+            <artifactId>asciidoctorj-screenshot</artifactId>
+          </dependency>
+          <dependency>
+            <groupId>org.asciidoctor</groupId>
+            <artifactId>asciidoctorj-diagram</artifactId>
+          </dependency>
+        </dependencies>
+      </plugin>
+    </plugins>
+    <pluginManagement>
+      <plugins>
+        <plugin>
+          <groupId>org.springframework.boot</groupId>
+          <artifactId>spring-boot-maven-plugin</artifactId>
+          <version>${spring-boot.version}</version>
+          <executions>
+            <execution>
+              <goals>
+                <goal>repackage</goal>
+              </goals>
+            </execution>
+          </executions>
+          <configuration>
+            <mainClass>${start-class}</mainClass>
+          </configuration>
+        </plugin>
+        <plugin>
+          <artifactId>maven-enforcer-plugin</artifactId>
+          <version>${maven-enforcer-plugin.version}</version>
+          <executions>
+            <execution>
+              <id>enforce-versions</id>
+              <goals>
+                <goal>enforce</goal>
+              </goals>
+            </execution>
+            <execution>
+              <id>enforce-dependencyConvergence</id>
+              <goals>
+                <goal>enforce</goal>
+              </goals>
+              <configuration>
+                <rules>
+                  <DependencyConvergence />
+                </rules>
+                <fail>false</fail>
+              </configuration>
+            </execution>
+          </executions>
+          <dependencies>
+            <dependency>
+              <groupId>io.jsonwebtoken</groupId>
+              <artifactId>jjwt-jackson</artifactId>
+              <version>${json-web-token.version}</version>
+              <exclusions>
+                <exclusion>
+                  <groupId>com.fasterxml.jackson.core</groupId>
+                  <artifactId>jackson-databind</artifactId>
+                </exclusion>
+              </exclusions>
+            </dependency>
+          </dependencies>
+          <configuration>
+            <rules>
+              <requireMavenVersion>
+                <message>You are running an older version of Maven. JHipster requires at least Maven ${maven.version}</message>
+                <version>[${maven.version},)</version>
+              </requireMavenVersion>
+              <requireJavaVersion>
+                <message>You are running an incompatible version of Java. JHipster engine supports JDK 21+.</message>
+                <version>[21,22)</version>
+              </requireJavaVersion>
+            </rules>
+          </configuration>
+        </plugin>
+      </plugins>
+    </pluginManagement>
+  </build>
+  <profiles>
+    <profile>
+      <id>local</id>
+      <activation />
+      <build>
+        <pluginManagement>
+          <plugins>
+            <plugin>
+              <artifactId>maven-enforcer-plugin</artifactId>
+              <version>${maven-enforcer-plugin.version}</version>
+              <executions>
+                <execution>
+                  <id>enforce-versions</id>
+                  <goals>
+                    <goal>enforce</goal>
+                  </goals>
+                </execution>
+                <execution>
+                  <id>enforce-dependencyConvergence</id>
+                  <goals>
+                    <goal>enforce</goal>
+                  </goals>
+                  <configuration>
+                    <rules>
+                      <DependencyConvergence />
+                    </rules>
+                    <fail>false</fail>
+                  </configuration>
+                </execution>
+              </executions>
+              <dependencies>
+                <dependency>
+                  <groupId>io.jsonwebtoken</groupId>
+                  <artifactId>jjwt-jackson</artifactId>
+                  <version>${json-web-token.version}</version>
+                  <exclusions>
+                    <exclusion>
+                      <groupId>com.fasterxml.jackson.core</groupId>
+                      <artifactId>jackson-databind</artifactId>
+                    </exclusion>
+                  </exclusions>
+                </dependency>
+              </dependencies>
+              <configuration>
+                <rules>
+                  <requireMavenVersion>
+                    <message>You are running an older version of Maven. JHipster requires at least Maven ${maven.version}</message>
+                    <version>[${maven.version},)</version>
+                  </requireMavenVersion>
+                  <requireJavaVersion>
+                    <message>You are running an incompatible version of Java. JHipster engine supports JDK 21+.</message>
+                    <version>[21,22)</version>
+                  </requireJavaVersion>
+                </rules>
+              </configuration>
+            </plugin>
+          </plugins>
+        </pluginManagement>
+        <plugins>
+          <plugin>
+            <artifactId>maven-enforcer-plugin</artifactId>
+          </plugin>
+        </plugins>
+      </build>
+      <properties>
+        <spring.profiles.active>local</spring.profiles.active>
+      </properties>
+      <dependencies>
+        <dependency>
+          <groupId>org.cassandraunit</groupId>
+          <artifactId>cassandra-unit</artifactId>
+          <version>${cassandraunit.version}</version>
+          <scope>test</scope>
+        </dependency>
+      </dependencies>
+    </profile>
+  </profiles>
+</project>
diff --git a/src/test/resources/tech/jhipster/lite/module/infrastructure/secondary/FileSystemJHipsterModulesRepositoryTest.shouldApplyTwoModulesToMavenProject.pom.xml.approved.txt b/src/test/resources/tech/jhipster/lite/module/infrastructure/secondary/FileSystemJHipsterModulesRepositoryTest.shouldApplyTwoModulesToMavenProject.pom.xml.approved.txt
new file mode 100644
index 000000000..64efb6360
--- /dev/null
+++ b/src/test/resources/tech/jhipster/lite/module/infrastructure/secondary/FileSystemJHipsterModulesRepositoryTest.shouldApplyTwoModulesToMavenProject.pom.xml.approved.txt
@@ -0,0 +1,260 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <artifactId>jhlite-test-maven-project</artifactId>
+  <version>0.0.1</version>
+  <name>Test project</name>
+  <packaging>pom</packaging>
+
+  <properties>
+    <json-web-token.version>0.12.6</json-web-token.version>
+    <logstash-logback-encoder.version>7.2</logstash-logback-encoder.version>
+    <spring-boot.version>3.3.3</spring-boot.version>
+    
+    
+    <maven-enforcer-plugin.version>3.5.0</maven-enforcer-plugin.version>
+    <spring-profiles-active>local</spring-profiles-active>
+    <cassandraunit.version>4.3.1.0</cassandraunit.version>
+    <commons-lang3.version>3.17.0</commons-lang3.version>
+    <reflections.version>0.10.2</reflections.version>
+  </properties>
+
+  <dependencyManagement>
+    <dependencies>
+      <dependency>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-dependencies</artifactId>
+        <version>${spring-boot.version}</version>
+        <type>pom</type>
+        <scope>import</scope>
+      </dependency>
+      <dependency>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-dependencies</artifactId>
+        <version>${spring-boot.version}</version>
+        <scope>import</scope>
+      </dependency>
+      <dependency>
+        <groupId>org.apache.commons</groupId>
+        <artifactId>commons-lang3</artifactId>
+        <version>${commons-lang3.version}</version>
+        <scope>import</scope>
+      </dependency>
+    </dependencies>
+  </dependencyManagement>
+
+  <dependencies>
+    <dependency>
+      <groupId>io.jsonwebtoken</groupId>
+      <artifactId>jjwt-api</artifactId>
+      <version>${json-web-token.version}</version>
+      <classifier>classif</classifier>
+      <scope>test</scope>
+      <optional>true</optional>
+    </dependency>
+    <dependency>
+      <groupId>org.springframework.boot</groupId>
+      <artifactId>spring-boot-starter</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>io.jsonwebtoken</groupId>
+      <artifactId>jjwt-api</artifactId>
+      <version>${json-web-token.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.springframework.boot</groupId>
+      <artifactId>spring-boot-starter-web</artifactId>
+      <exclusions>
+        <exclusion>
+          <groupId>org.springframework.boot</groupId>
+          <artifactId>spring-boot-starter-tomcat</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+    <dependency>
+      <groupId>org.reflections</groupId>
+      <artifactId>reflections</artifactId>
+      <version>${reflections.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.junit.jupiter</groupId>
+      <artifactId>junit-jupiter-engine</artifactId>
+      <version>${spring-boot.version}</version>
+      <classifier>test</classifier>
+      <scope>test</scope>
+      <optional>true</optional>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-maven-plugin</artifactId>
+      </plugin>
+      <plugin>
+        <artifactId>maven-enforcer-plugin</artifactId>
+      </plugin>
+      <plugin>
+        <groupId>org.asciidoctor</groupId>
+        <artifactId>asciidoctor-maven-plugin</artifactId>
+        <dependencies>
+          <dependency>
+            <groupId>org.asciidoctor</groupId>
+            <artifactId>asciidoctorj-screenshot</artifactId>
+          </dependency>
+          <dependency>
+            <groupId>org.asciidoctor</groupId>
+            <artifactId>asciidoctorj-diagram</artifactId>
+          </dependency>
+        </dependencies>
+      </plugin>
+    </plugins>
+    <pluginManagement>
+      <plugins>
+        <plugin>
+          <groupId>org.springframework.boot</groupId>
+          <artifactId>spring-boot-maven-plugin</artifactId>
+          <version>${spring-boot.version}</version>
+          <executions>
+            <execution>
+              <goals>
+                <goal>repackage</goal>
+              </goals>
+            </execution>
+          </executions>
+          <configuration>
+            <mainClass>${start-class}</mainClass>
+          </configuration>
+        </plugin>
+        <plugin>
+          <artifactId>maven-enforcer-plugin</artifactId>
+          <version>${maven-enforcer-plugin.version}</version>
+          <executions>
+            <execution>
+              <id>enforce-versions</id>
+              <goals>
+                <goal>enforce</goal>
+              </goals>
+            </execution>
+            <execution>
+              <id>enforce-dependencyConvergence</id>
+              <goals>
+                <goal>enforce</goal>
+              </goals>
+              <configuration>
+                <rules>
+                  <DependencyConvergence />
+                </rules>
+                <fail>false</fail>
+              </configuration>
+            </execution>
+          </executions>
+          <dependencies>
+            <dependency>
+              <groupId>io.jsonwebtoken</groupId>
+              <artifactId>jjwt-jackson</artifactId>
+              <version>${json-web-token.version}</version>
+              <exclusions>
+                <exclusion>
+                  <groupId>com.fasterxml.jackson.core</groupId>
+                  <artifactId>jackson-databind</artifactId>
+                </exclusion>
+              </exclusions>
+            </dependency>
+          </dependencies>
+          <configuration>
+            <rules>
+              <requireMavenVersion>
+                <message>You are running an older version of Maven. JHipster requires at least Maven ${maven.version}</message>
+                <version>[${maven.version},)</version>
+              </requireMavenVersion>
+              <requireJavaVersion>
+                <message>You are running an incompatible version of Java. JHipster engine supports JDK 21+.</message>
+                <version>[21,22)</version>
+              </requireJavaVersion>
+            </rules>
+          </configuration>
+        </plugin>
+      </plugins>
+    </pluginManagement>
+  </build>
+  <profiles>
+    <profile>
+      <id>local</id>
+      <activation />
+      <build>
+        <pluginManagement>
+          <plugins>
+            <plugin>
+              <artifactId>maven-enforcer-plugin</artifactId>
+              <version>${maven-enforcer-plugin.version}</version>
+              <executions>
+                <execution>
+                  <id>enforce-versions</id>
+                  <goals>
+                    <goal>enforce</goal>
+                  </goals>
+                </execution>
+                <execution>
+                  <id>enforce-dependencyConvergence</id>
+                  <goals>
+                    <goal>enforce</goal>
+                  </goals>
+                  <configuration>
+                    <rules>
+                      <DependencyConvergence />
+                    </rules>
+                    <fail>false</fail>
+                  </configuration>
+                </execution>
+              </executions>
+              <dependencies>
+                <dependency>
+                  <groupId>io.jsonwebtoken</groupId>
+                  <artifactId>jjwt-jackson</artifactId>
+                  <version>${json-web-token.version}</version>
+                  <exclusions>
+                    <exclusion>
+                      <groupId>com.fasterxml.jackson.core</groupId>
+                      <artifactId>jackson-databind</artifactId>
+                    </exclusion>
+                  </exclusions>
+                </dependency>
+              </dependencies>
+              <configuration>
+                <rules>
+                  <requireMavenVersion>
+                    <message>You are running an older version of Maven. JHipster requires at least Maven ${maven.version}</message>
+                    <version>[${maven.version},)</version>
+                  </requireMavenVersion>
+                  <requireJavaVersion>
+                    <message>You are running an incompatible version of Java. JHipster engine supports JDK 21+.</message>
+                    <version>[21,22)</version>
+                  </requireJavaVersion>
+                </rules>
+              </configuration>
+            </plugin>
+          </plugins>
+        </pluginManagement>
+        <plugins>
+          <plugin>
+            <artifactId>maven-enforcer-plugin</artifactId>
+          </plugin>
+        </plugins>
+      </build>
+      <properties>
+        <spring.profiles.active>local</spring.profiles.active>
+      </properties>
+      <dependencies>
+        <dependency>
+          <groupId>org.cassandraunit</groupId>
+          <artifactId>cassandra-unit</artifactId>
+          <version>${cassandraunit.version}</version>
+          <scope>test</scope>
+        </dependency>
+      </dependencies>
+    </profile>
+  </profiles>
+</project>