Repository: jhipster/jhipster-lite (java)

Issue:
Implement the rest api which return the presets stored at `src/main/resources`
Should create the endpoint GET `/api/presets`. See #9491 description for the complete roadmap.

Pull request description:
return presets through endpoint
Fix #10317 

Patch:
diff --git a/src/main/java/tech/jhipster/lite/project/application/ProjectsApplicationService.java b/src/main/java/tech/jhipster/lite/project/application/ProjectsApplicationService.java
index 75fa74ddb..b92b22027 100644
--- a/src/main/java/tech/jhipster/lite/project/application/ProjectsApplicationService.java
+++ b/src/main/java/tech/jhipster/lite/project/application/ProjectsApplicationService.java
@@ -1,5 +1,6 @@
 package tech.jhipster.lite.project.application;
 
+import java.util.Collection;
 import org.springframework.stereotype.Service;
 import tech.jhipster.lite.project.domain.ProjectPath;
 import tech.jhipster.lite.project.domain.ProjectsRepository;
@@ -8,6 +9,7 @@ import tech.jhipster.lite.project.domain.download.ProjectsDownloader;
 import tech.jhipster.lite.project.domain.history.ProjectActionToAppend;
 import tech.jhipster.lite.project.domain.history.ProjectActionsAppender;
 import tech.jhipster.lite.project.domain.history.ProjectHistory;
+import tech.jhipster.lite.project.domain.preset.Preset;
 import tech.jhipster.lite.shared.projectfolder.domain.ProjectFolder;
 
 @Service
@@ -39,4 +41,8 @@ public class ProjectsApplicationService {
   public ProjectHistory getHistory(ProjectPath path) {
     return projects.getHistory(path);
   }
+
+  public Collection<Preset> getPresets() {
+    return projects.getPresets();
+  }
 }
diff --git a/src/main/java/tech/jhipster/lite/project/infrastructure/primary/ProjectsResource.java b/src/main/java/tech/jhipster/lite/project/infrastructure/primary/ProjectsResource.java
index f814b3544..a9de80f19 100644
--- a/src/main/java/tech/jhipster/lite/project/infrastructure/primary/ProjectsResource.java
+++ b/src/main/java/tech/jhipster/lite/project/infrastructure/primary/ProjectsResource.java
@@ -8,11 +8,7 @@ import org.springframework.core.io.Resource;
 import org.springframework.http.HttpHeaders;
 import org.springframework.http.MediaType;
 import org.springframework.http.ResponseEntity;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
 import tech.jhipster.lite.project.application.ProjectsApplicationService;
 import tech.jhipster.lite.project.domain.ProjectPath;
 import tech.jhipster.lite.project.domain.download.Project;
@@ -55,4 +51,10 @@ class ProjectsResource {
   ) {
     return ResponseEntity.ok(RestProjectHistory.from(projects.getHistory(new ProjectPath(path))));
   }
+
+  @Operation(summary = "Get presets configuration")
+  @GetMapping(path = "/presets", produces = MediaType.APPLICATION_JSON_VALUE)
+  ResponseEntity<RestPresets> getPresets() {
+    return ResponseEntity.ok(RestPresets.from(projects.getPresets()));
+  }
 }
diff --git a/src/main/java/tech/jhipster/lite/project/infrastructure/primary/RestModuleToApply.java b/src/main/java/tech/jhipster/lite/project/infrastructure/primary/RestModuleToApply.java
new file mode 100644
index 000000000..b006d820c
--- /dev/null
+++ b/src/main/java/tech/jhipster/lite/project/infrastructure/primary/RestModuleToApply.java
@@ -0,0 +1,11 @@
+package tech.jhipster.lite.project.infrastructure.primary;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import tech.jhipster.lite.project.domain.ModuleSlug;
+
+@Schema(name = "ModuleToApply", description = "Information for a module to apply")
+record RestModuleToApply(@Schema(description = "Slug of the module to apply") String slug) {
+  public static RestModuleToApply from(ModuleSlug moduleSlug) {
+    return new RestModuleToApply(moduleSlug.get());
+  }
+}
diff --git a/src/main/java/tech/jhipster/lite/project/infrastructure/primary/RestPreset.java b/src/main/java/tech/jhipster/lite/project/infrastructure/primary/RestPreset.java
new file mode 100644
index 000000000..d44e777a7
--- /dev/null
+++ b/src/main/java/tech/jhipster/lite/project/infrastructure/primary/RestPreset.java
@@ -0,0 +1,15 @@
+package tech.jhipster.lite.project.infrastructure.primary;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import java.util.Collection;
+import tech.jhipster.lite.project.domain.preset.Preset;
+
+@Schema(name = "Preset", description = "Information on specific configuration")
+record RestPreset(
+  @Schema(description = "Name of specific configuration") String name,
+  @Schema(description = "Modules to apply of this specific configuration") Collection<RestModuleToApply> modules
+) {
+  public static RestPreset from(Preset preset) {
+    return new RestPreset(preset.name().name(), preset.modules().modules().stream().map(RestModuleToApply::from).toList());
+  }
+}
diff --git a/src/main/java/tech/jhipster/lite/project/infrastructure/primary/RestPresets.java b/src/main/java/tech/jhipster/lite/project/infrastructure/primary/RestPresets.java
new file mode 100644
index 000000000..e3e4a2825
--- /dev/null
+++ b/src/main/java/tech/jhipster/lite/project/infrastructure/primary/RestPresets.java
@@ -0,0 +1,12 @@
+package tech.jhipster.lite.project.infrastructure.primary;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import java.util.Collection;
+import tech.jhipster.lite.project.domain.preset.Preset;
+
+@Schema(name = "Presets", description = "Information on the predefined configurations")
+record RestPresets(Collection<RestPreset> presets) {
+  public static RestPresets from(Collection<Preset> presets) {
+    return new RestPresets(presets.stream().map(RestPreset::from).toList());
+  }
+}
diff --git a/src/main/java/tech/jhipster/lite/project/infrastructure/secondary/FileSystemProjectsRepository.java b/src/main/java/tech/jhipster/lite/project/infrastructure/secondary/FileSystemProjectsRepository.java
index cdc5d4373..be5c1dc38 100644
--- a/src/main/java/tech/jhipster/lite/project/infrastructure/secondary/FileSystemProjectsRepository.java
+++ b/src/main/java/tech/jhipster/lite/project/infrastructure/secondary/FileSystemProjectsRepository.java
@@ -25,6 +25,7 @@ class FileSystemProjectsRepository implements ProjectsRepository {
 
   private static final String HISTORY_FOLDER = ".jhipster/modules";
   private static final String HISTORY_FILE = "history.json";
+  private static final String PRESET_FOLDER = "/";
   private static final String PRESET_FILE = "preset.json";
 
   private final ObjectMapper json;
@@ -94,9 +95,13 @@ class FileSystemProjectsRepository implements ProjectsRepository {
   @Override
   public Collection<Preset> getPresets() {
     try {
-      return json.readValue(projectFiles.readBytes(PRESET_FILE), PersistedPresets.class).toDomain();
+      return json.readValue(projectFiles.readBytes(presetFilePath()), PersistedPresets.class).toDomain();
     } catch (IOException e) {
       throw GeneratorException.technicalError("Can't read presets: " + e.getMessage(), e);
     }
   }
+
+  private String presetFilePath() {
+    return PRESET_FOLDER + PRESET_FILE;
+  }
 }


Test patch:
diff --git a/src/test/features/projects.feature b/src/test/features/projects.feature
index 4d33592bf..bb5598444 100644
--- a/src/test/features/projects.feature
+++ b/src/test/features/projects.feature
@@ -19,3 +19,14 @@ Feature: project management
       | init |
     And I should have properties
       | projectName | Test project |
+
+  Scenario: Should get presets definition
+    When I get the presets definition
+    Then I should have preset names
+      | test preset one |
+      | test preset two |
+    And I should have preset modules
+      | test-module-one   |
+      | test-module-two   |
+      | test-module-three |
+      | test-module-four  |
diff --git a/src/test/java/tech/jhipster/lite/cucumber/CucumberConfiguration.java b/src/test/java/tech/jhipster/lite/cucumber/CucumberConfiguration.java
index 3d010eff7..35a5b75ed 100644
--- a/src/test/java/tech/jhipster/lite/cucumber/CucumberConfiguration.java
+++ b/src/test/java/tech/jhipster/lite/cucumber/CucumberConfiguration.java
@@ -8,21 +8,21 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
 import org.springframework.boot.test.web.client.TestRestTemplate;
-import org.springframework.http.client.BufferingClientHttpRequestFactory;
-import org.springframework.http.client.ClientHttpRequestFactory;
-import org.springframework.http.client.ClientHttpRequestInterceptor;
-import org.springframework.http.client.ClientHttpResponse;
-import org.springframework.http.client.SimpleClientHttpRequestFactory;
+import org.springframework.http.client.*;
 import org.springframework.http.converter.StringHttpMessageConverter;
 import org.springframework.test.context.ActiveProfiles;
 import org.springframework.web.client.RestTemplate;
 import tech.jhipster.lite.JHLiteApp;
 import tech.jhipster.lite.cucumber.rest.CucumberRestTestContext;
+import tech.jhipster.lite.project.infrastructure.secondary.FakedFileSystemProjectFilesConfiguration;
 import tech.jhipster.lite.project.infrastructure.secondary.MockedProjectFormatterConfiguration;
 
 @ActiveProfiles("test")
 @CucumberContextConfiguration
-@SpringBootTest(classes = { JHLiteApp.class, MockedProjectFormatterConfiguration.class }, webEnvironment = WebEnvironment.RANDOM_PORT)
+@SpringBootTest(
+  classes = { JHLiteApp.class, MockedProjectFormatterConfiguration.class, FakedFileSystemProjectFilesConfiguration.class },
+  webEnvironment = WebEnvironment.RANDOM_PORT
+)
 public class CucumberConfiguration {
 
   @Autowired
diff --git a/src/test/java/tech/jhipster/lite/project/infrastructure/primary/ProjectsSteps.java b/src/test/java/tech/jhipster/lite/project/infrastructure/primary/ProjectsSteps.java
index 3553ad839..84251af5f 100644
--- a/src/test/java/tech/jhipster/lite/project/infrastructure/primary/ProjectsSteps.java
+++ b/src/test/java/tech/jhipster/lite/project/infrastructure/primary/ProjectsSteps.java
@@ -1,9 +1,10 @@
 package tech.jhipster.lite.project.infrastructure.primary;
 
-import static org.assertj.core.api.Assertions.assertThat;
-import static tech.jhipster.lite.TestProjects.lastProjectFolder;
-import static tech.jhipster.lite.cucumber.rest.CucumberRestAssertions.assertThatLastResponse;
+import static org.assertj.core.api.Assertions.*;
+import static tech.jhipster.lite.TestProjects.*;
+import static tech.jhipster.lite.cucumber.rest.CucumberRestAssertions.*;
 
+import io.cucumber.java.en.And;
 import io.cucumber.java.en.Then;
 import io.cucumber.java.en.When;
 import java.io.IOException;
@@ -12,7 +13,6 @@ import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.List;
 import java.util.Map;
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.web.client.TestRestTemplate;
 import org.springframework.http.HttpEntity;
 import org.springframework.http.HttpHeaders;
@@ -22,8 +22,11 @@ import tech.jhipster.lite.module.infrastructure.secondary.git.GitTestUtil;
 
 public class ProjectsSteps {
 
-  @Autowired
-  private TestRestTemplate rest;
+  private final TestRestTemplate rest;
+
+  public ProjectsSteps(TestRestTemplate rest) {
+    this.rest = rest;
+  }
 
   @When("I download the created project")
   public void downloadCreatedProject() {
@@ -116,4 +119,19 @@ public class ProjectsSteps {
   private Path lastProjectPath() {
     return Paths.get(lastProjectFolder());
   }
+
+  @When("I get the presets definition")
+  public void getPresetsDefinition() {
+    rest.exchange("/api/presets", HttpMethod.GET, new HttpEntity<>(jsonHeaders()), Void.class);
+  }
+
+  @Then("I should have preset names")
+  public void shouldHavePresetName(List<String> names) {
+    assertThatLastResponse().hasOkStatus().hasElement("$.presets.*.name").withValues(names);
+  }
+
+  @And("I should have preset modules")
+  public void shouldHavePresetModules(List<String> modules) {
+    assertThatLastResponse().hasOkStatus().hasElement("$.presets.*.modules.*.slug").withValues(modules);
+  }
 }
diff --git a/src/test/java/tech/jhipster/lite/project/infrastructure/primary/RestPresetsTest.java b/src/test/java/tech/jhipster/lite/project/infrastructure/primary/RestPresetsTest.java
new file mode 100644
index 000000000..4c33725d2
--- /dev/null
+++ b/src/test/java/tech/jhipster/lite/project/infrastructure/primary/RestPresetsTest.java
@@ -0,0 +1,58 @@
+package tech.jhipster.lite.project.infrastructure.primary;
+
+import static org.assertj.core.api.Assertions.*;
+
+import java.util.Collection;
+import java.util.List;
+import org.junit.jupiter.api.Test;
+import tech.jhipster.lite.JsonHelper;
+import tech.jhipster.lite.UnitTest;
+import tech.jhipster.lite.project.domain.ModuleSlug;
+import tech.jhipster.lite.project.domain.ModulesSlugs;
+import tech.jhipster.lite.project.domain.preset.Preset;
+import tech.jhipster.lite.project.domain.preset.PresetName;
+
+@UnitTest
+class RestPresetsTest {
+
+  @Test
+  void shouldSerializeToJson() {
+    assertThat(JsonHelper.writeAsString(RestPresets.from(presets()))).isEqualTo(json());
+  }
+
+  private static Collection<Preset> presets() {
+    return List.of(
+      new Preset(
+        new PresetName("test preset one"),
+        new ModulesSlugs(List.of(new ModuleSlug("test-module-one"), new ModuleSlug("test-module-two")))
+      ),
+      new Preset(
+        new PresetName("test preset two"),
+        new ModulesSlugs(List.of(new ModuleSlug("test-module-three"), new ModuleSlug("test-module-four")))
+      )
+    );
+  }
+
+  private static String json() {
+    return """
+    {\
+    "presets":[\
+    {\
+    "name":"test preset one",\
+    "modules":[\
+    {"slug":"test-module-one"},\
+    {"slug":"test-module-two"}\
+    ]\
+    },\
+    {\
+    "name":"test preset two",\
+    "modules":[\
+    {"slug":"test-module-three"},\
+    {"slug":"test-module-four"}\
+    ]\
+    }\
+    ]\
+    }\
+    """;
+  }
+}
diff --git a/src/test/java/tech/jhipster/lite/project/infrastructure/secondary/FakedFileSystemProjectFilesConfiguration.java b/src/test/java/tech/jhipster/lite/project/infrastructure/secondary/FakedFileSystemProjectFilesConfiguration.java
new file mode 100644
index 000000000..442c36288
--- /dev/null
+++ b/src/test/java/tech/jhipster/lite/project/infrastructure/secondary/FakedFileSystemProjectFilesConfiguration.java
@@ -0,0 +1,42 @@
+package tech.jhipster.lite.project.infrastructure.secondary;
+
+import java.nio.charset.StandardCharsets;
+import org.mockito.Mockito;
+import org.springframework.boot.test.context.TestConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Primary;
+import tech.jhipster.lite.module.infrastructure.secondary.FileSystemProjectFiles;
+
+@TestConfiguration
+public class FakedFileSystemProjectFilesConfiguration {
+
+  @Bean
+  @Primary
+  FileSystemProjectFiles fileSystemProjectFiles() {
+    FileSystemProjectFiles fileSystemProjectFiles = Mockito.spy(new FileSystemProjectFiles());
+
+    mockPresetJson(fileSystemProjectFiles);
+
+    return fileSystemProjectFiles;
+  }
+
+  private static void mockPresetJson(FileSystemProjectFiles fileSystemProjectFiles) {
+    String presetJsonContent =
+      """
+      {
+        "presets": [
+          {
+            "name": "test preset one",
+            "modules": ["test-module-one", "test-module-two"]
+          },
+          {
+            "name": "test preset two",
+            "modules": ["test-module-three", "test-module-four"]
+          }
+        ]
+      }
+      """;
+
+    Mockito.doReturn(presetJsonContent.getBytes(StandardCharsets.UTF_8)).when(fileSystemProjectFiles).readBytes("/preset.json");
+  }
+}
diff --git a/src/test/java/tech/jhipster/lite/project/infrastructure/secondary/FileSystemProjectsRepositoryTest.java b/src/test/java/tech/jhipster/lite/project/infrastructure/secondary/FileSystemProjectsRepositoryTest.java
index c5124ffd6..4a968abec 100644
--- a/src/test/java/tech/jhipster/lite/project/infrastructure/secondary/FileSystemProjectsRepositoryTest.java
+++ b/src/test/java/tech/jhipster/lite/project/infrastructure/secondary/FileSystemProjectsRepositoryTest.java
@@ -332,7 +332,7 @@ class FileSystemProjectsRepositoryTest {
         ]
       }
       """;
-    lenient().when(projectFiles.readBytes("preset.json")).thenReturn(validPresetJson.getBytes());
+    lenient().when(projectFiles.readBytes("/preset.json")).thenReturn(validPresetJson.getBytes());
 
     return projectFiles;
   }