From 67f7c78a7d5d596910a38e1a4b97ff3b3d46c689 Mon Sep 17 00:00:00 2001
From: "Zickermann, Jan" <jan.zickermann@dataport.de>
Date: Tue, 30 Jan 2024 18:31:22 +0100
Subject: [PATCH] OZG-4815 OZG-4832 Add links from root controller to
 `/api/config`

---
 pom.xml                                       |  4 +
 .../de/ozgcloud/admin/RootController.java     |  7 +-
 .../de/ozgcloud/admin/RootModelAssembler.java | 50 ++++++++++++
 src/main/resources/application.yaml           |  2 +-
 .../java/de/ozgcloud/admin/ApiRootITCase.java |  2 +-
 .../de/ozgcloud/admin/RootControllerTest.java |  5 ++
 .../admin/RootModelAssemblerTest.java         | 80 +++++++++++++++++++
 .../de/ozgcloud/admin/RootTestFactory.java    | 13 ++-
 8 files changed, 158 insertions(+), 5 deletions(-)
 create mode 100644 src/main/java/de/ozgcloud/admin/RootModelAssembler.java
 create mode 100644 src/test/java/de/ozgcloud/admin/RootModelAssemblerTest.java

diff --git a/pom.xml b/pom.xml
index 1d4c685b..54fd4499 100644
--- a/pom.xml
+++ b/pom.xml
@@ -38,6 +38,10 @@
 			<groupId>org.springframework.boot</groupId>
 			<artifactId>spring-boot-starter-data-rest</artifactId>
 		</dependency>
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-hateoas</artifactId>
+		</dependency>
 
 		<!-- Dev -->
 		<dependency>
diff --git a/src/main/java/de/ozgcloud/admin/RootController.java b/src/main/java/de/ozgcloud/admin/RootController.java
index bf17388b..9e7ffb3b 100644
--- a/src/main/java/de/ozgcloud/admin/RootController.java
+++ b/src/main/java/de/ozgcloud/admin/RootController.java
@@ -1,6 +1,7 @@
 package de.ozgcloud.admin;
 
 import org.springframework.boot.info.BuildProperties;
+import org.springframework.hateoas.EntityModel;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
@@ -15,9 +16,11 @@ public class RootController {
 
 	private final BuildProperties buildProperties;
 
+	private final RootModelAssembler modelAssembler;
+
 	@GetMapping
-	public Root getRoot() {
-		return buildRoot();
+	public EntityModel<Root> getRoot() {
+		return modelAssembler.toModel(buildRoot());
 	}
 
 	private Root buildRoot() {
diff --git a/src/main/java/de/ozgcloud/admin/RootModelAssembler.java b/src/main/java/de/ozgcloud/admin/RootModelAssembler.java
new file mode 100644
index 00000000..3be41d94
--- /dev/null
+++ b/src/main/java/de/ozgcloud/admin/RootModelAssembler.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2024. Das Land Schleswig-Holstein vertreten durch das Ministerium für Energiewende, Klimaschutz, Umwelt und Natur
+ * Zentrales IT-Management
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+package de.ozgcloud.admin;
+
+import org.springframework.boot.autoconfigure.data.rest.RepositoryRestProperties;
+import org.springframework.hateoas.EntityModel;
+import org.springframework.hateoas.Link;
+import org.springframework.hateoas.server.RepresentationModelAssembler;
+import org.springframework.hateoas.server.mvc.WebMvcLinkBuilder;
+import org.springframework.stereotype.Component;
+
+import lombok.RequiredArgsConstructor;
+
+@Component
+@RequiredArgsConstructor
+public class RootModelAssembler implements RepresentationModelAssembler<Root, EntityModel<Root>> {
+	static final String REL_CONFIGURATION = "configuration";
+
+	private final RepositoryRestProperties restProperties;
+
+	@Override
+	public EntityModel<Root> toModel(Root root) {
+		var rootLink = WebMvcLinkBuilder.linkTo(RootController.class);
+		var configLink = rootLink.toUriComponentsBuilder().replacePath(restProperties.getBasePath());
+		return EntityModel.of(
+				root,
+				Link.of(configLink.toUriString(), REL_CONFIGURATION),
+				rootLink.withSelfRel()
+		);
+	}
+}
diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml
index 2db35bd1..fcfef4c5 100644
--- a/src/main/resources/application.yaml
+++ b/src/main/resources/application.yaml
@@ -1,4 +1,4 @@
 spring:
   data:
     rest:
-      basePath: /api
\ No newline at end of file
+      basePath: /api/config
\ No newline at end of file
diff --git a/src/test/java/de/ozgcloud/admin/ApiRootITCase.java b/src/test/java/de/ozgcloud/admin/ApiRootITCase.java
index 209eb0aa..e03bccde 100644
--- a/src/test/java/de/ozgcloud/admin/ApiRootITCase.java
+++ b/src/test/java/de/ozgcloud/admin/ApiRootITCase.java
@@ -53,7 +53,7 @@ class ApiRootITCase {
 		void shouldBetSetToApi() {
 			var basePath = restProperties.getBasePath();
 
-			assertEquals("/api", basePath);
+			assertEquals("/api/config", basePath);
 		}
 
 		@Test
diff --git a/src/test/java/de/ozgcloud/admin/RootControllerTest.java b/src/test/java/de/ozgcloud/admin/RootControllerTest.java
index f2e35aae..eb76ca0e 100644
--- a/src/test/java/de/ozgcloud/admin/RootControllerTest.java
+++ b/src/test/java/de/ozgcloud/admin/RootControllerTest.java
@@ -14,6 +14,7 @@ import org.mockito.Mock;
 import org.mockito.Spy;
 import org.mockito.junit.jupiter.MockitoExtension;
 import org.springframework.boot.info.BuildProperties;
+import org.springframework.hateoas.EntityModel;
 import org.springframework.test.web.servlet.MockMvc;
 import org.springframework.test.web.servlet.ResultActions;
 import org.springframework.test.web.servlet.setup.MockMvcBuilders;
@@ -30,10 +31,14 @@ class RootControllerTest {
 	@Mock
 	private BuildProperties buildProperties;
 
+	@Mock
+	private RootModelAssembler modelAssembler;
+
 	private MockMvc mockMvc;
 
 	@BeforeEach
 	void mock() {
+		when(modelAssembler.toModel(any())).thenAnswer(a -> EntityModel.of(a.getArgument(0)));
 		mockMvc = MockMvcBuilders.standaloneSetup(rootController).build();
 	}
 
diff --git a/src/test/java/de/ozgcloud/admin/RootModelAssemblerTest.java b/src/test/java/de/ozgcloud/admin/RootModelAssemblerTest.java
new file mode 100644
index 00000000..9f9e6d6c
--- /dev/null
+++ b/src/test/java/de/ozgcloud/admin/RootModelAssemblerTest.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2024. Das Land Schleswig-Holstein vertreten durch das Ministerium für Energiewende, Klimaschutz, Umwelt und Natur
+ * Zentrales IT-Management
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+package de.ozgcloud.admin;
+
+import static de.ozgcloud.admin.RootModelAssembler.*;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import java.util.Optional;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.boot.autoconfigure.data.rest.RepositoryRestProperties;
+import org.springframework.hateoas.IanaLinkRelations;
+import org.springframework.hateoas.Link;
+
+@ExtendWith(MockitoExtension.class)
+class RootModelAssemblerTest {
+
+	private static final String BASE_PATH = "/api/base";
+	@Spy
+	@InjectMocks
+	private RootModelAssembler modelAssembler;
+
+	@Mock
+	private RepositoryRestProperties restProperties;
+
+	@BeforeEach
+	void mockBasePath() {
+		when(restProperties.getBasePath()).thenReturn(BASE_PATH);
+	}
+
+	@Nested
+	class TestLinks {
+
+		@DisplayName("should have href to Spring Data REST base path")
+		@Test
+		void shouldHaveConfigBaseLink() {
+			var root = RootTestFactory.create();
+
+			assertEquals(Optional.of(Link.of(BASE_PATH, REL_CONFIGURATION)), modelAssembler.toModel(root).getLink(REL_CONFIGURATION));
+		}
+
+		@DisplayName("should have href to self")
+		@Test
+		void shouldHaveSelfLink() {
+			var root = RootTestFactory.create();
+
+			assertEquals(Optional.of(Link.of(RootController.PATH)), modelAssembler.toModel(root).getLink(IanaLinkRelations.SELF));
+		}
+
+	}
+
+}
\ No newline at end of file
diff --git a/src/test/java/de/ozgcloud/admin/RootTestFactory.java b/src/test/java/de/ozgcloud/admin/RootTestFactory.java
index e0879c75..4c78550e 100644
--- a/src/test/java/de/ozgcloud/admin/RootTestFactory.java
+++ b/src/test/java/de/ozgcloud/admin/RootTestFactory.java
@@ -10,5 +10,16 @@ public class RootTestFactory {
 	public static final String JAVA_VERSION = "1";
 	public static final String BUILD_VERSION = "2";
 	public static final String BUILD_NUMBER = "3";
-	
+
+	public static Root create() {
+		return createBuilder().build();
+	}
+
+	public static Root.RootBuilder createBuilder() {
+		return Root.builder()
+				.buildTime(BUILD_TIME)
+				.javaVersion(JAVA_VERSION)
+				.buildVersion(BUILD_VERSION)
+				.buildNumber(BUILD_NUMBER);
+	}
 }
-- 
GitLab