diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c10cce3..b1bbf1b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,4 +39,4 @@ jobs: run: ./gradlew build --no-daemon - name: Check coverage - run: ./gradlew jacocoTestReport jacocoTestCoverageVerification \ No newline at end of file + run: ./gradlew jacocoTestReport jacocoTestCoverageVerification diff --git a/build.gradle.kts b/build.gradle.kts index 39d70a2..22e14b5 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,15 +2,16 @@ plugins { java id("org.springframework.boot") version "3.3.3" id("io.spring.dependency-management") version "1.1.6" + kotlin("jvm") version "2.0.20" + kotlin("plugin.spring") version "2.0.20" + jacoco } group = "com.parkingapp" version = "0.0.1-SNAPSHOT" java { - toolchain { - languageVersion = JavaLanguageVersion.of(21) - } + sourceCompatibility = JavaVersion.VERSION_21 } repositories { @@ -18,11 +19,126 @@ repositories { } dependencies { + val REST_ASSURED = "5.5.0" + implementation("org.springframework.boot:spring-boot-starter") + implementation("org.springframework.boot:spring-boot-starter-web") + implementation("org.springframework.boot:spring-boot-starter-validation") + implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0") + implementation("org.springdoc:springdoc-openapi-ui:1.8.0") + implementation("org.springframework.boot:spring-boot-starter-jdbc") + + // Lombok + compileOnly("org.projectlombok:lombok:1.18.34") + annotationProcessor("org.projectlombok:lombok:1.18.34") + + // Lombok + testCompileOnly("org.projectlombok:lombok:1.18.34") + testAnnotationProcessor("org.projectlombok:lombok:1.18.34") + + + // Test dependencies testImplementation("org.springframework.boot:spring-boot-starter-test") + testImplementation(platform("org.junit:junit-bom:5.11.0")) + testImplementation("org.junit.jupiter:junit-jupiter") testRuntimeOnly("org.junit.platform:junit-platform-launcher") + testImplementation("org.assertj:assertj-core:3.26.3") + testImplementation("org.mockito:mockito-core:5.+") + + testImplementation(platform("org.testcontainers:testcontainers-bom:1.20.1")) + testImplementation("org.testcontainers:junit-jupiter") + testImplementation("org.testcontainers:postgresql") + testImplementation("io.rest-assured:rest-assured:$REST_ASSURED") + testImplementation("io.rest-assured:json-path:$REST_ASSURED") + testImplementation("io.rest-assured:xml-path:$REST_ASSURED") + testImplementation("io.rest-assured:spring-mock-mvc:$REST_ASSURED") + testImplementation("io.rest-assured:spring-commons:$REST_ASSURED") + testImplementation("org.wiremock:wiremock-standalone:3.9.1") + testImplementation("org.awaitility:awaitility:4.2.2") + testImplementation("com.tngtech.archunit:archunit:1.3.0") +} + +jacoco { + toolVersion = "0.8.12" } -tasks.withType { - useJUnitPlatform() +tasks.apply { + test { + enableAssertions = true + useJUnitPlatform { + excludeTags("integration") + excludeTags("component") + excludeTags("contract") + } + finalizedBy(jacocoTestReport) + } + + task("integrationTest") { + group = "verification" + description = "Runs integration tests." + useJUnitPlatform { + includeTags("integration") + } + shouldRunAfter(test) + } + + task("contractTest") { + group = "verification" + description = "Runs contract tests." + useJUnitPlatform { + includeTags("contract") + } + shouldRunAfter("integrationTest") + } + + task("componentTest") { + group = "verification" + description = "Runs component tests." + useJUnitPlatform { + includeTags("component") + } + shouldRunAfter("contractTest") + } + + check { + dependsOn("integrationTest", "contractTest", "componentTest") + } + + jacocoTestReport { + val jacocoDir = layout.buildDirectory.dir("jacoco") + executionData( + fileTree(jacocoDir).include( + "/test.exec", + "/contractTest.exec", + "/integrationTest.exec" + ) + ) + reports { + csv.required.set(false) + html.required.set(true) + xml.required.set(true) + html.outputLocation.set(layout.buildDirectory.dir("jacoco/html")) + xml.outputLocation.set(layout.buildDirectory.file("jacoco/report.xml")) + } + dependsOn(test, "integrationTest", "contractTest") + } + + jacocoTestCoverageVerification { + val jacocoDir = layout.buildDirectory.dir("jacoco") + executionData( + fileTree(jacocoDir).include( + "test.exec", + "contractTest.exec", + "integrationTest.exec" + ) + ) + violationRules { + rule { + limit { + minimum = "0.00".toBigDecimal() + } + } + } + dependsOn(test, "integrationTest", "contractTest") + } } diff --git a/src/main/java/com/parkingapp/notificationservice/infrastructure/config/ObjectMapperConfig.java b/src/main/java/com/parkingapp/notificationservice/infrastructure/config/ObjectMapperConfig.java new file mode 100644 index 0000000..3dba829 --- /dev/null +++ b/src/main/java/com/parkingapp/notificationservice/infrastructure/config/ObjectMapperConfig.java @@ -0,0 +1,30 @@ +package com.parkingapp.notificationservice.infrastructure.config; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; + +@Configuration +public class ObjectMapperConfig { + @Bean + @Primary + public ObjectMapper objectMapper() { + return new ObjectMapper() + .setSerializationInclusion(JsonInclude.Include.NON_NULL) + .setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE) + .configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true) + .configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true) + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true) + .configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES, false) + .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false) + .configure(SerializationFeature.INDENT_OUTPUT, true) + .registerModule(new JavaTimeModule()); + } +} diff --git a/src/test/java/com/parkingapp/notificationservice/NotificationServiceApplicationTests.java b/src/test/java/com/parkingapp/notificationservice/NotificationServiceApplicationTests.java deleted file mode 100644 index 7ebad13..0000000 --- a/src/test/java/com/parkingapp/notificationservice/NotificationServiceApplicationTests.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.parkingapp.notificationservice; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class NotificationServiceApplicationTests { - - @Test - void contextLoads() { - } - -} diff --git a/src/test/resources/application-test.yml b/src/test/resources/application-test.yml new file mode 100644 index 0000000..f2b43d4 --- /dev/null +++ b/src/test/resources/application-test.yml @@ -0,0 +1,12 @@ +spring: + flyway: + enabled: true + baselineOnMigrate: true + locations: classpath:db/migrations + +clients: + payment: + url: http://localhost:${wiremock.port:1080} + timeout: + connect: 1000 + read: 1000