diff --git a/build.gradle b/build.gradle index 6851f70..9b02f34 100644 --- a/build.gradle +++ b/build.gradle @@ -11,7 +11,6 @@ apply from: 'lint.gradle' allprojects { group = "${projectGroup}" version = "${applicationVersion}" - sourceCompatibility = project.javaVersion repositories { mavenCentral() @@ -31,7 +30,19 @@ subprojects { } dependencies { - testImplementation 'org.springframework.boot:spring-boot-starter-test' + annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor' + testImplementation 'org.springframework.boot:spring-boot-test' + testImplementation 'org.junit.jupiter:junit-jupiter-api' + testImplementation 'org.junit.jupiter:junit-jupiter-engine' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + testImplementation 'org.assertj:assertj-core' + testImplementation 'org.mockito:mockito-core' + } + + java { + toolchain { + languageVersion = JavaLanguageVersion.of(project.javaVersion) + } } bootJar.enabled = false @@ -71,7 +82,7 @@ subprojects { } } - tasks.named('asciidoctor') { + tasks.named('asciidoctor').configure { dependsOn restDocsTest } } \ No newline at end of file diff --git a/core/core-api/build.gradle b/core/core-api/build.gradle index 1e81465..3531baa 100644 --- a/core/core-api/build.gradle +++ b/core/core-api/build.gradle @@ -10,5 +10,5 @@ dependencies { testImplementation project(":tests:api-docs") - implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-webmvc' } \ No newline at end of file diff --git a/core/core-api/src/main/java/io/dodn/springboot/core/api/controller/v1/ExampleController.java b/core/core-api/src/main/java/io/dodn/springboot/core/api/controller/v1/ExampleController.java index 8391b97..b7cda1a 100644 --- a/core/core-api/src/main/java/io/dodn/springboot/core/api/controller/v1/ExampleController.java +++ b/core/core-api/src/main/java/io/dodn/springboot/core/api/controller/v1/ExampleController.java @@ -1,6 +1,7 @@ package io.dodn.springboot.core.api.controller.v1; import io.dodn.springboot.core.api.controller.v1.request.ExampleRequestDto; +import io.dodn.springboot.core.api.controller.v1.response.ExampleItemResponseDto; import io.dodn.springboot.core.api.controller.v1.response.ExampleResponseDto; import io.dodn.springboot.core.domain.ExampleData; import io.dodn.springboot.core.domain.ExampleResult; @@ -14,6 +15,9 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import java.time.LocalDate; +import java.time.LocalDateTime; + @RestController public class ExampleController { @@ -27,13 +31,15 @@ public ExampleController(ExampleService exampleExampleService) { public ApiResponse exampleGet(@PathVariable String exampleValue, @RequestParam String exampleParam) { ExampleResult result = exampleExampleService.processExample(new ExampleData(exampleValue, exampleParam)); - return ApiResponse.success(new ExampleResponseDto(result.data())); + return ApiResponse.success(new ExampleResponseDto(result.data(), LocalDate.now(), LocalDateTime.now(), + ExampleItemResponseDto.build())); } @PostMapping("/post") public ApiResponse examplePost(@RequestBody ExampleRequestDto request) { ExampleResult result = exampleExampleService.processExample(request.toExampleData()); - return ApiResponse.success(new ExampleResponseDto(result.data())); + return ApiResponse.success(new ExampleResponseDto(result.data(), LocalDate.now(), LocalDateTime.now(), + ExampleItemResponseDto.build())); } } diff --git a/core/core-api/src/main/java/io/dodn/springboot/core/api/controller/v1/response/ExampleItemResponseDto.java b/core/core-api/src/main/java/io/dodn/springboot/core/api/controller/v1/response/ExampleItemResponseDto.java new file mode 100644 index 0000000..74cce9e --- /dev/null +++ b/core/core-api/src/main/java/io/dodn/springboot/core/api/controller/v1/response/ExampleItemResponseDto.java @@ -0,0 +1,9 @@ +package io.dodn.springboot.core.api.controller.v1.response; + +import java.util.List; + +public record ExampleItemResponseDto(String key) { + public static List build() { + return List.of(new ExampleItemResponseDto("1"), new ExampleItemResponseDto("2")); + } +} \ No newline at end of file diff --git a/core/core-api/src/main/java/io/dodn/springboot/core/api/controller/v1/response/ExampleResponseDto.java b/core/core-api/src/main/java/io/dodn/springboot/core/api/controller/v1/response/ExampleResponseDto.java index 3c034c5..30ffe1d 100644 --- a/core/core-api/src/main/java/io/dodn/springboot/core/api/controller/v1/response/ExampleResponseDto.java +++ b/core/core-api/src/main/java/io/dodn/springboot/core/api/controller/v1/response/ExampleResponseDto.java @@ -1,4 +1,9 @@ package io.dodn.springboot.core.api.controller.v1.response; -public record ExampleResponseDto(String result) { +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; + +public record ExampleResponseDto(String result, LocalDate date, LocalDateTime datetime, + List items) { } diff --git a/core/core-api/src/test/java/io/dodn/springboot/core/api/controller/v1/ExampleControllerTest.java b/core/core-api/src/test/java/io/dodn/springboot/core/api/controller/v1/ExampleControllerTest.java index fda6eb9..30829a5 100644 --- a/core/core-api/src/test/java/io/dodn/springboot/core/api/controller/v1/ExampleControllerTest.java +++ b/core/core-api/src/test/java/io/dodn/springboot/core/api/controller/v1/ExampleControllerTest.java @@ -1,74 +1,79 @@ package io.dodn.springboot.core.api.controller.v1; -import static io.dodn.springboot.test.api.RestDocsUtils.requestPreprocessor; -import static io.dodn.springboot.test.api.RestDocsUtils.responsePreprocessor; +import io.dodn.springboot.core.api.controller.v1.request.ExampleRequestDto; +import io.dodn.springboot.core.domain.ExampleResult; +import io.dodn.springboot.core.domain.ExampleService; +import io.dodn.springboot.test.api.RestDocsTest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.http.MediaType; +import org.springframework.restdocs.operation.preprocess.Preprocessors; +import org.springframework.restdocs.payload.JsonFieldType; +import tools.jackson.databind.json.JsonMapper; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; import static org.springframework.restdocs.request.RequestDocumentation.queryParameters; - -import io.dodn.springboot.core.api.controller.v1.request.ExampleRequestDto; -import io.dodn.springboot.core.domain.ExampleResult; -import io.dodn.springboot.core.domain.ExampleService; -import io.dodn.springboot.test.api.RestDocsTest; -import io.restassured.http.ContentType; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.http.HttpStatus; -import org.springframework.restdocs.payload.JsonFieldType; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; public class ExampleControllerTest extends RestDocsTest { private ExampleService exampleService; - private ExampleController controller; - @BeforeEach public void setUp() { exampleService = mock(ExampleService.class); - controller = new ExampleController(exampleService); - mockMvc = mockController(controller); + mockMvc = mockController(new ExampleController(exampleService)); } @Test - public void exampleGet() { + public void exampleGet() throws Exception { when(exampleService.processExample(any())).thenReturn(new ExampleResult("BYE")); - given().contentType(ContentType.JSON) - .queryParam("exampleParam", "HELLO_PARAM") - .get("/get/{exampleValue}", "HELLO_PATH") - .then() - .status(HttpStatus.OK) - .apply(document("exampleGet", requestPreprocessor(), responsePreprocessor(), + mockMvc + .perform(get("/get/{exampleValue}", "HELLO_PATH").param("exampleParam", "HELLO_PARAM") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andDo(document("exampleGet", Preprocessors.preprocessRequest(Preprocessors.prettyPrint()), + Preprocessors.preprocessResponse(Preprocessors.prettyPrint()), pathParameters(parameterWithName("exampleValue").description("ExampleValue")), queryParameters(parameterWithName("exampleParam").description("ExampleParam")), responseFields(fieldWithPath("result").type(JsonFieldType.STRING).description("ResultType"), - fieldWithPath("data.result").type(JsonFieldType.STRING).description("Result Date"), + fieldWithPath("data.result").type(JsonFieldType.STRING).description("Result Data"), + fieldWithPath("data.date").type(JsonFieldType.STRING).description("Result Date"), + fieldWithPath("data.datetime").type(JsonFieldType.STRING).description("Result Datetime"), + fieldWithPath("data.items").type(JsonFieldType.ARRAY).description("Result Items"), + fieldWithPath("data.items[].key").type(JsonFieldType.STRING).description("Result Item"), fieldWithPath("error").type(JsonFieldType.NULL).ignored()))); } @Test - public void examplePost() { + public void examplePost() throws Exception { when(exampleService.processExample(any())).thenReturn(new ExampleResult("BYE")); - given().contentType(ContentType.JSON) - .body(new ExampleRequestDto("HELLO_BODY")) - .post("/post") - .then() - .status(HttpStatus.OK) - .apply(document("examplePost", requestPreprocessor(), responsePreprocessor(), + mockMvc + .perform(post("/post").contentType(MediaType.APPLICATION_JSON) + .content(JsonMapper.builder().build().writeValueAsString(new ExampleRequestDto("HELLO_BODY")))) + .andExpect(status().isOk()) + .andDo(document("examplePost", Preprocessors.preprocessRequest(Preprocessors.prettyPrint()), + Preprocessors.preprocessResponse(Preprocessors.prettyPrint()), requestFields( fieldWithPath("data").type(JsonFieldType.STRING).description("ExampleBody Data Field")), responseFields(fieldWithPath("result").type(JsonFieldType.STRING).description("ResultType"), - fieldWithPath("data.result").type(JsonFieldType.STRING).description("Result Date"), + fieldWithPath("data.result").type(JsonFieldType.STRING).description("Result Data"), + fieldWithPath("data.date").type(JsonFieldType.STRING).description("Result Date"), + fieldWithPath("data.datetime").type(JsonFieldType.STRING).description("Result Datetime"), + fieldWithPath("data.items").type(JsonFieldType.ARRAY).description("Result Items"), + fieldWithPath("data.items[].key").type(JsonFieldType.STRING).description("Result Item"), fieldWithPath("error").type(JsonFieldType.STRING).ignored()))); } diff --git a/gradle.properties b/gradle.properties index 3e7aa4a..8ecff55 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,16 +3,16 @@ applicationVersion=0.0.1-SNAPSHOT ### Project configs ### projectGroup=io.dodn.springboot -javaVersion=21 +javaVersion=25 ### Plugin dependency versions ### -asciidoctorConvertVersion=3.3.2 +asciidoctorConvertVersion=4.0.5 springJavaFormatVersion=0.0.47 ### Spring dependency versions ### -springBootVersion=3.5.3 +springBootVersion=4.0.0 springDependencyManagementVersion=1.1.7 -springCloudDependenciesVersion=2025.0.0 +springCloudDependenciesVersion=2025.1.0 ### External dependency versions ### -sentryVersion=8.17.0 +sentryVersion=8.27.1 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index a4b76b9..1b33c55 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index d4081da..23449a2 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index f5feea6..23d15a9 100755 --- a/gradlew +++ b/gradlew @@ -86,8 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s -' "$PWD" ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -115,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -206,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -214,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/gradlew.bat b/gradlew.bat index 9b42019..5eed7ee 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -70,11 +70,11 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/storage/db-core/src/main/java/io/dodn/springboot/storage/db/core/config/CoreJpaConfig.java b/storage/db-core/src/main/java/io/dodn/springboot/storage/db/core/config/CoreJpaConfig.java index aaac11f..c66c122 100644 --- a/storage/db-core/src/main/java/io/dodn/springboot/storage/db/core/config/CoreJpaConfig.java +++ b/storage/db-core/src/main/java/io/dodn/springboot/storage/db/core/config/CoreJpaConfig.java @@ -1,6 +1,6 @@ package io.dodn.springboot.storage.db.core.config; -import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.boot.persistence.autoconfigure.EntityScan; import org.springframework.context.annotation.Configuration; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.transaction.annotation.EnableTransactionManagement; diff --git a/support/logging/build.gradle b/support/logging/build.gradle index ba61015..85d7e61 100644 --- a/support/logging/build.gradle +++ b/support/logging/build.gradle @@ -1,4 +1,4 @@ dependencies { - implementation 'io.micrometer:micrometer-tracing-bridge-brave' + implementation 'org.springframework.boot:spring-boot-starter-opentelemetry' implementation "io.sentry:sentry-logback:${property("sentryVersion")}" } diff --git a/support/logging/src/main/resources/logging.yml b/support/logging/src/main/resources/logging.yml index 8f932ad..4558f9b 100644 --- a/support/logging/src/main/resources/logging.yml +++ b/support/logging/src/main/resources/logging.yml @@ -1 +1,3 @@ -logging.config: classpath:logback/logback-${spring.profiles.active}.xml \ No newline at end of file +logging.config: classpath:logback/logback-${spring.profiles.active}.xml + +management.otlp.metrics.export.enabled: false \ No newline at end of file diff --git a/support/monitoring/build.gradle b/support/monitoring/build.gradle index 73f8731..d4838dd 100644 --- a/support/monitoring/build.gradle +++ b/support/monitoring/build.gradle @@ -1,4 +1,4 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-actuator' - implementation 'io.micrometer:micrometer-registry-prometheus' + runtimeOnly 'io.micrometer:micrometer-registry-prometheus' } diff --git a/tests/api-docs/build.gradle b/tests/api-docs/build.gradle index ecfa37b..fcac351 100644 --- a/tests/api-docs/build.gradle +++ b/tests/api-docs/build.gradle @@ -1,8 +1,5 @@ dependencies { - compileOnly 'jakarta.servlet:jakarta.servlet-api' - compileOnly 'org.springframework.boot:spring-boot-starter-test' - compileOnly 'com.fasterxml.jackson.core:jackson-databind' + api 'org.springframework.boot:spring-boot-restdocs' api 'org.springframework.restdocs:spring-restdocs-mockmvc' - api 'org.springframework.restdocs:spring-restdocs-restassured' - api 'io.rest-assured:spring-mock-mvc' + compileOnly 'org.junit.jupiter:junit-jupiter-api' } diff --git a/tests/api-docs/src/main/java/io/dodn/springboot/test/api/RestDocsTest.java b/tests/api-docs/src/main/java/io/dodn/springboot/test/api/RestDocsTest.java index 144ab84..80725b9 100644 --- a/tests/api-docs/src/main/java/io/dodn/springboot/test/api/RestDocsTest.java +++ b/tests/api-docs/src/main/java/io/dodn/springboot/test/api/RestDocsTest.java @@ -1,15 +1,8 @@ package io.dodn.springboot.test.api; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; - -import io.restassured.module.mockmvc.RestAssuredMockMvc; -import io.restassured.module.mockmvc.specification.MockMvcRequestSpecification; - import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.restdocs.RestDocumentationContextProvider; import org.springframework.restdocs.RestDocumentationExtension; import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation; @@ -20,7 +13,7 @@ @ExtendWith(RestDocumentationExtension.class) public abstract class RestDocsTest { - protected MockMvcRequestSpecification mockMvc; + protected MockMvc mockMvc; private RestDocumentationContextProvider restDocumentation; @@ -29,28 +22,10 @@ public void setUp(RestDocumentationContextProvider restDocumentation) { this.restDocumentation = restDocumentation; } - protected MockMvcRequestSpecification given() { - return mockMvc; - } - - protected MockMvcRequestSpecification mockController(Object controller) { - MockMvc mockMvc = createMockMvc(controller); - return RestAssuredMockMvc.given().mockMvc(mockMvc); - } - - private MockMvc createMockMvc(Object controller) { - MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(objectMapper()); - + protected MockMvc mockController(Object controller) { return MockMvcBuilders.standaloneSetup(controller) .apply(MockMvcRestDocumentation.documentationConfiguration(restDocumentation)) - .setMessageConverters(converter) .build(); } - private ObjectMapper objectMapper() { - return new ObjectMapper().findAndRegisterModules() - .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) - .disable(SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS); - } - } diff --git a/tests/api-docs/src/main/java/io/dodn/springboot/test/api/RestDocsUtils.java b/tests/api-docs/src/main/java/io/dodn/springboot/test/api/RestDocsUtils.java deleted file mode 100644 index c11ae12..0000000 --- a/tests/api-docs/src/main/java/io/dodn/springboot/test/api/RestDocsUtils.java +++ /dev/null @@ -1,19 +0,0 @@ -package io.dodn.springboot.test.api; - -import org.springframework.restdocs.operation.preprocess.OperationRequestPreprocessor; -import org.springframework.restdocs.operation.preprocess.OperationResponsePreprocessor; -import org.springframework.restdocs.operation.preprocess.Preprocessors; - -public class RestDocsUtils { - - public static OperationRequestPreprocessor requestPreprocessor() { - return Preprocessors.preprocessRequest( - Preprocessors.modifyUris().scheme("http").host("dev.dodn.io").removePort(), - Preprocessors.prettyPrint()); - } - - public static OperationResponsePreprocessor responsePreprocessor() { - return Preprocessors.preprocessResponse(Preprocessors.prettyPrint()); - } - -}