Refactor Ping service tests and introduce PingProperties configuration for cleaner service name handling
This commit is contained in:
+11
@@ -0,0 +1,11 @@
|
|||||||
|
package at.mocode.ping.infrastructure
|
||||||
|
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties
|
||||||
|
import org.springframework.stereotype.Component
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@ConfigurationProperties(prefix = "app")
|
||||||
|
data class PingProperties(
|
||||||
|
var serviceName: String = "ping-service",
|
||||||
|
var serviceNameFallback: String = "ping-service-fallback"
|
||||||
|
)
|
||||||
+7
-11
@@ -2,6 +2,7 @@ package at.mocode.ping.infrastructure.web
|
|||||||
|
|
||||||
import at.mocode.ping.api.*
|
import at.mocode.ping.api.*
|
||||||
import at.mocode.ping.application.PingUseCase
|
import at.mocode.ping.application.PingUseCase
|
||||||
|
import at.mocode.ping.infrastructure.PingProperties
|
||||||
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker
|
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import org.springframework.security.access.prepost.PreAuthorize
|
import org.springframework.security.access.prepost.PreAuthorize
|
||||||
@@ -18,10 +19,10 @@ import kotlin.uuid.ExperimentalUuidApi
|
|||||||
* Nutzt den Application Port (PingUseCase).
|
* Nutzt den Application Port (PingUseCase).
|
||||||
*/
|
*/
|
||||||
@RestController
|
@RestController
|
||||||
// @CrossOrigin Annotation entfernt. CORS wird zentral im API-Gateway gehandhabt.
|
|
||||||
@OptIn(ExperimentalUuidApi::class)
|
@OptIn(ExperimentalUuidApi::class)
|
||||||
class PingController(
|
class PingController(
|
||||||
private val pingUseCase: PingUseCase
|
private val pingUseCase: PingUseCase,
|
||||||
|
private val properties: PingProperties
|
||||||
) : PingApi {
|
) : PingApi {
|
||||||
|
|
||||||
private val logger = LoggerFactory.getLogger(PingController::class.java)
|
private val logger = LoggerFactory.getLogger(PingController::class.java)
|
||||||
@@ -54,7 +55,7 @@ class PingController(
|
|||||||
return EnhancedPingResponse(
|
return EnhancedPingResponse(
|
||||||
status = "pong",
|
status = "pong",
|
||||||
timestamp = domainPing.timestamp.atOffset(ZoneOffset.UTC).format(formatter),
|
timestamp = domainPing.timestamp.atOffset(ZoneOffset.UTC).format(formatter),
|
||||||
service = "ping-service",
|
service = properties.serviceName,
|
||||||
circuitBreakerState = "CLOSED",
|
circuitBreakerState = "CLOSED",
|
||||||
responseTime = elapsedMs
|
responseTime = elapsedMs
|
||||||
)
|
)
|
||||||
@@ -93,7 +94,7 @@ class PingController(
|
|||||||
private fun createResponse(domainPing: at.mocode.ping.domain.Ping, status: String) = PingResponse(
|
private fun createResponse(domainPing: at.mocode.ping.domain.Ping, status: String) = PingResponse(
|
||||||
status = status,
|
status = status,
|
||||||
timestamp = domainPing.timestamp.atOffset(ZoneOffset.UTC).format(formatter),
|
timestamp = domainPing.timestamp.atOffset(ZoneOffset.UTC).format(formatter),
|
||||||
service = "ping-service"
|
service = properties.serviceName
|
||||||
)
|
)
|
||||||
|
|
||||||
// Fallback
|
// Fallback
|
||||||
@@ -103,7 +104,7 @@ class PingController(
|
|||||||
return EnhancedPingResponse(
|
return EnhancedPingResponse(
|
||||||
status = "fallback",
|
status = "fallback",
|
||||||
timestamp = java.time.OffsetDateTime.now().format(formatter),
|
timestamp = java.time.OffsetDateTime.now().format(formatter),
|
||||||
service = "ping-service-fallback",
|
service = properties.serviceNameFallback,
|
||||||
circuitBreakerState = "OPEN",
|
circuitBreakerState = "OPEN",
|
||||||
responseTime = 0
|
responseTime = 0
|
||||||
)
|
)
|
||||||
@@ -114,13 +115,8 @@ class PingController(
|
|||||||
return HealthResponse(
|
return HealthResponse(
|
||||||
status = "up",
|
status = "up",
|
||||||
timestamp = java.time.OffsetDateTime.now().format(formatter),
|
timestamp = java.time.OffsetDateTime.now().format(formatter),
|
||||||
service = "ping-service",
|
service = properties.serviceName,
|
||||||
healthy = true
|
healthy = true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/ping/history")
|
|
||||||
fun getHistory() = pingUseCase.getPingHistory().map {
|
|
||||||
mapOf("id" to it.id.toString(), "message" to it.message, "time" to it.timestamp.toString())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,3 +80,10 @@ resilience4j:
|
|||||||
instances:
|
instances:
|
||||||
pingCircuitBreaker:
|
pingCircuitBreaker:
|
||||||
base-config: default
|
base-config: default
|
||||||
|
|
||||||
|
# ==========================================================================
|
||||||
|
# Custom Application Properties
|
||||||
|
# ==========================================================================
|
||||||
|
app:
|
||||||
|
service-name: ${spring.application.name}
|
||||||
|
service-name-fallback: "${spring.application.name}-fallback"
|
||||||
|
|||||||
+141
@@ -0,0 +1,141 @@
|
|||||||
|
package at.mocode.ping.application
|
||||||
|
|
||||||
|
import at.mocode.ping.domain.Ping
|
||||||
|
import at.mocode.ping.domain.PingRepository
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.verify
|
||||||
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
|
import org.junit.jupiter.api.BeforeEach
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import java.time.Instant
|
||||||
|
import kotlin.uuid.ExperimentalUuidApi
|
||||||
|
import kotlin.uuid.Uuid
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit-Tests für den PingService (Application Layer).
|
||||||
|
* Testet alle Use-Case-Methoden isoliert mit MockK.
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalUuidApi::class)
|
||||||
|
class PingServiceTest {
|
||||||
|
|
||||||
|
private val repository: PingRepository = mockk()
|
||||||
|
private lateinit var service: PingService
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
fun setUp() {
|
||||||
|
service = PingService(repository)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `executePing should persist and return ping with given message`() {
|
||||||
|
// Given
|
||||||
|
every { repository.save(any()) } answers { firstArg() }
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = service.executePing("Hello")
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assertThat(result.message).isEqualTo("Hello")
|
||||||
|
verify { repository.save(any()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `executePing should generate a new UUID for each ping`() {
|
||||||
|
// Given
|
||||||
|
every { repository.save(any()) } answers { firstArg() }
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result1 = service.executePing("Ping 1")
|
||||||
|
val result2 = service.executePing("Ping 2")
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assertThat(result1.id).isNotEqualTo(result2.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `getPingHistory should delegate to repository and return all pings`() {
|
||||||
|
// Given
|
||||||
|
val pings = listOf(
|
||||||
|
Ping(message = "Ping A"),
|
||||||
|
Ping(message = "Ping B")
|
||||||
|
)
|
||||||
|
every { repository.findAll() } returns pings
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = service.getPingHistory()
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assertThat(result).hasSize(2)
|
||||||
|
assertThat(result.map { it.message }).containsExactly("Ping A", "Ping B")
|
||||||
|
verify { repository.findAll() }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `getPingHistory should return empty list when no pings exist`() {
|
||||||
|
// Given
|
||||||
|
every { repository.findAll() } returns emptyList()
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = service.getPingHistory()
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assertThat(result).isEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `getPing should return ping by id`() {
|
||||||
|
// Given
|
||||||
|
val id = Uuid.generateV7()
|
||||||
|
val ping = Ping(id = id, message = "Find me")
|
||||||
|
every { repository.findById(id) } returns ping
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = service.getPing(id)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assertThat(result).isEqualTo(ping)
|
||||||
|
verify { repository.findById(id) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `getPing should return null when ping not found`() {
|
||||||
|
// Given
|
||||||
|
val id = Uuid.generateV7()
|
||||||
|
every { repository.findById(id) } returns null
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = service.getPing(id)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assertThat(result).isNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `getPingsSince should return pings after given timestamp`() {
|
||||||
|
// Given
|
||||||
|
val timestamp = Instant.parse("2024-01-01T00:00:00Z").toEpochMilli()
|
||||||
|
val ping = Ping(message = "Recent Ping", timestamp = Instant.parse("2024-06-01T00:00:00Z"))
|
||||||
|
every { repository.findByTimestampAfter(any()) } returns listOf(ping)
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = service.getPingsSince(timestamp)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assertThat(result).hasSize(1)
|
||||||
|
assertThat(result[0].message).isEqualTo("Recent Ping")
|
||||||
|
verify { repository.findByTimestampAfter(Instant.ofEpochMilli(timestamp)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `getPingsSince should return empty list when no pings after timestamp`() {
|
||||||
|
// Given
|
||||||
|
every { repository.findByTimestampAfter(any()) } returns emptyList()
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = service.getPingsSince(System.currentTimeMillis())
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assertThat(result).isEmpty()
|
||||||
|
}
|
||||||
|
}
|
||||||
+55
-32
@@ -1,11 +1,12 @@
|
|||||||
package at.mocode.ping.service
|
package at.mocode.ping.infrastructure.web
|
||||||
|
|
||||||
import at.mocode.ping.application.PingUseCase
|
import at.mocode.ping.application.PingUseCase
|
||||||
import at.mocode.ping.domain.Ping
|
import at.mocode.ping.domain.Ping
|
||||||
|
import at.mocode.ping.infrastructure.PingProperties
|
||||||
import at.mocode.ping.infrastructure.persistence.PingRepositoryAdapter
|
import at.mocode.ping.infrastructure.persistence.PingRepositoryAdapter
|
||||||
import at.mocode.ping.infrastructure.web.PingController
|
|
||||||
import at.mocode.ping.test.TestPingServiceApplication
|
import at.mocode.ping.test.TestPingServiceApplication
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
import io.mockk.clearMocks
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import io.mockk.verify
|
import io.mockk.verify
|
||||||
@@ -32,8 +33,8 @@ import java.time.Instant
|
|||||||
import kotlin.uuid.ExperimentalUuidApi
|
import kotlin.uuid.ExperimentalUuidApi
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unit tests for PingController
|
* Unit-Test für den PingController (Web Layer).
|
||||||
* Tests REST endpoints with mocked dependencies
|
* Nutzt @WebMvcTest für einen isolierten MVC-Slice ohne echte Services oder DB.
|
||||||
*/
|
*/
|
||||||
@WebMvcTest(
|
@WebMvcTest(
|
||||||
controllers = [PingController::class],
|
controllers = [PingController::class],
|
||||||
@@ -42,7 +43,7 @@ import kotlin.uuid.ExperimentalUuidApi
|
|||||||
@ContextConfiguration(classes = [TestPingServiceApplication::class])
|
@ContextConfiguration(classes = [TestPingServiceApplication::class])
|
||||||
@ActiveProfiles("test")
|
@ActiveProfiles("test")
|
||||||
@Import(PingControllerTest.PingControllerTestConfig::class)
|
@Import(PingControllerTest.PingControllerTestConfig::class)
|
||||||
@AutoConfigureMockMvc(addFilters = false) // Disable security filters for unit tests
|
@AutoConfigureMockMvc(addFilters = false)
|
||||||
@OptIn(ExperimentalUuidApi::class)
|
@OptIn(ExperimentalUuidApi::class)
|
||||||
class PingControllerTest {
|
class PingControllerTest {
|
||||||
|
|
||||||
@@ -53,6 +54,9 @@ class PingControllerTest {
|
|||||||
@Qualifier("pingUseCaseMock")
|
@Qualifier("pingUseCaseMock")
|
||||||
private lateinit var pingUseCase: PingUseCase
|
private lateinit var pingUseCase: PingUseCase
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private lateinit var properties: PingProperties
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private lateinit var objectMapper: ObjectMapper
|
private lateinit var objectMapper: ObjectMapper
|
||||||
|
|
||||||
@@ -65,12 +69,17 @@ class PingControllerTest {
|
|||||||
@Bean
|
@Bean
|
||||||
@Primary
|
@Primary
|
||||||
fun pingRepositoryAdapter(): PingRepositoryAdapter = mockk(relaxed = true)
|
fun pingRepositoryAdapter(): PingRepositoryAdapter = mockk(relaxed = true)
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@Primary
|
||||||
|
fun pingProperties(): PingProperties = mockk(relaxed = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
// Reset mocks before each test
|
clearMocks(pingUseCase, properties)
|
||||||
io.mockk.clearMocks(pingUseCase)
|
every { properties.serviceName } returns "ping-service"
|
||||||
|
every { properties.serviceNameFallback } returns "ping-service-fallback"
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -81,7 +90,7 @@ class PingControllerTest {
|
|||||||
timestamp = Instant.parse("2023-10-01T10:00:00Z")
|
timestamp = Instant.parse("2023-10-01T10:00:00Z")
|
||||||
)
|
)
|
||||||
|
|
||||||
// When & Then
|
// When
|
||||||
val mvcResult: MvcResult = mockMvc.perform(get("/ping/simple"))
|
val mvcResult: MvcResult = mockMvc.perform(get("/ping/simple"))
|
||||||
.andExpect(request().asyncStarted())
|
.andExpect(request().asyncStarted())
|
||||||
.andReturn()
|
.andReturn()
|
||||||
@@ -90,12 +99,10 @@ class PingControllerTest {
|
|||||||
.andExpect(status().isOk)
|
.andExpect(status().isOk)
|
||||||
.andReturn()
|
.andReturn()
|
||||||
|
|
||||||
val body = result.response.contentAsString
|
// Then
|
||||||
val json = objectMapper.readTree(body)
|
val json = objectMapper.readTree(result.response.contentAsString)
|
||||||
assertThat(json.has("status")).isTrue
|
|
||||||
assertThat(json["status"].asText()).isEqualTo("pong")
|
assertThat(json["status"].asText()).isEqualTo("pong")
|
||||||
assertThat(json["service"].asText()).isEqualTo("ping-service")
|
assertThat(json["service"].asText()).isEqualTo(properties.serviceName)
|
||||||
|
|
||||||
verify { pingUseCase.executePing("Simple Ping") }
|
verify { pingUseCase.executePing("Simple Ping") }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,7 +114,7 @@ class PingControllerTest {
|
|||||||
timestamp = Instant.parse("2023-10-01T10:00:00Z")
|
timestamp = Instant.parse("2023-10-01T10:00:00Z")
|
||||||
)
|
)
|
||||||
|
|
||||||
// When & Then
|
// When
|
||||||
val mvcResult: MvcResult = mockMvc.perform(get("/ping/enhanced"))
|
val mvcResult: MvcResult = mockMvc.perform(get("/ping/enhanced"))
|
||||||
.andExpect(request().asyncStarted())
|
.andExpect(request().asyncStarted())
|
||||||
.andReturn()
|
.andReturn()
|
||||||
@@ -116,18 +123,16 @@ class PingControllerTest {
|
|||||||
.andExpect(status().isOk)
|
.andExpect(status().isOk)
|
||||||
.andReturn()
|
.andReturn()
|
||||||
|
|
||||||
val body = result.response.contentAsString
|
// Then
|
||||||
val json = objectMapper.readTree(body)
|
val json = objectMapper.readTree(result.response.contentAsString)
|
||||||
assertThat(json.has("status")).isTrue
|
|
||||||
assertThat(json["status"].asText()).isEqualTo("pong")
|
assertThat(json["status"].asText()).isEqualTo("pong")
|
||||||
assertThat(json["service"].asText()).isEqualTo("ping-service")
|
assertThat(json["service"].asText()).isEqualTo(properties.serviceName)
|
||||||
|
|
||||||
verify { pingUseCase.executePing("Enhanced Ping") }
|
verify { pingUseCase.executePing("Enhanced Ping") }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `should return health check response`() {
|
fun `should return health check response with status up`() {
|
||||||
// When & Then
|
// When
|
||||||
val mvcResult: MvcResult = mockMvc.perform(get("/ping/health"))
|
val mvcResult: MvcResult = mockMvc.perform(get("/ping/health"))
|
||||||
.andExpect(request().asyncStarted())
|
.andExpect(request().asyncStarted())
|
||||||
.andReturn()
|
.andReturn()
|
||||||
@@ -136,17 +141,16 @@ class PingControllerTest {
|
|||||||
.andExpect(status().isOk)
|
.andExpect(status().isOk)
|
||||||
.andReturn()
|
.andReturn()
|
||||||
|
|
||||||
val body = result.response.contentAsString
|
// Then
|
||||||
val json = objectMapper.readTree(body)
|
val json = objectMapper.readTree(result.response.contentAsString)
|
||||||
assertThat(json.has("status")).isTrue
|
|
||||||
assertThat(json["status"].asText()).isEqualTo("up")
|
assertThat(json["status"].asText()).isEqualTo("up")
|
||||||
assertThat(json["service"].asText()).isEqualTo("ping-service")
|
assertThat(json["service"].asText()).isEqualTo(properties.serviceName)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `should return sync pings`() {
|
fun `should return sync pings as list`() {
|
||||||
// Given
|
// Given
|
||||||
val timestamp = 1696154400000L // 2023-10-01T10:00:00Z
|
val timestamp = 1696154400000L
|
||||||
every { pingUseCase.getPingsSince(timestamp) } returns listOf(
|
every { pingUseCase.getPingsSince(timestamp) } returns listOf(
|
||||||
Ping(
|
Ping(
|
||||||
message = "Sync Ping",
|
message = "Sync Ping",
|
||||||
@@ -154,8 +158,7 @@ class PingControllerTest {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
// When & Then
|
// When
|
||||||
// Changed parameter name to 'since' to match the controller update
|
|
||||||
val mvcResult: MvcResult = mockMvc.perform(get("/ping/sync").param("since", timestamp.toString()))
|
val mvcResult: MvcResult = mockMvc.perform(get("/ping/sync").param("since", timestamp.toString()))
|
||||||
.andExpect(request().asyncStarted())
|
.andExpect(request().asyncStarted())
|
||||||
.andReturn()
|
.andReturn()
|
||||||
@@ -164,13 +167,33 @@ class PingControllerTest {
|
|||||||
.andExpect(status().isOk)
|
.andExpect(status().isOk)
|
||||||
.andReturn()
|
.andReturn()
|
||||||
|
|
||||||
val body = result.response.contentAsString
|
// Then
|
||||||
val json = objectMapper.readTree(body)
|
val json = objectMapper.readTree(result.response.contentAsString)
|
||||||
assertThat(json.isArray).isTrue
|
assertThat(json.isArray).isTrue
|
||||||
assertThat(json.size()).isEqualTo(1)
|
assertThat(json.size()).isEqualTo(1)
|
||||||
assertThat(json[0]["message"].asText()).isEqualTo("Sync Ping")
|
assertThat(json[0]["message"].asText()).isEqualTo("Sync Ping")
|
||||||
assertThat(json[0]["lastModified"].asLong()).isEqualTo(timestamp + 1000)
|
assertThat(json[0]["lastModified"].asLong()).isEqualTo(timestamp + 1000)
|
||||||
|
|
||||||
verify { pingUseCase.getPingsSince(timestamp) }
|
verify { pingUseCase.getPingsSince(timestamp) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `should return empty list when no pings since timestamp`() {
|
||||||
|
// Given
|
||||||
|
val timestamp = System.currentTimeMillis()
|
||||||
|
every { pingUseCase.getPingsSince(timestamp) } returns emptyList()
|
||||||
|
|
||||||
|
// When
|
||||||
|
val mvcResult: MvcResult = mockMvc.perform(get("/ping/sync").param("since", timestamp.toString()))
|
||||||
|
.andExpect(request().asyncStarted())
|
||||||
|
.andReturn()
|
||||||
|
|
||||||
|
val result = mockMvc.perform(asyncDispatch(mvcResult))
|
||||||
|
.andExpect(status().isOk)
|
||||||
|
.andReturn()
|
||||||
|
|
||||||
|
// Then
|
||||||
|
val json = objectMapper.readTree(result.response.contentAsString)
|
||||||
|
assertThat(json.isArray).isTrue
|
||||||
|
assertThat(json.size()).isEqualTo(0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
-74
@@ -1,74 +0,0 @@
|
|||||||
package at.mocode.ping.service
|
|
||||||
|
|
||||||
import at.mocode.ping.application.PingUseCase
|
|
||||||
import at.mocode.ping.domain.Ping
|
|
||||||
import at.mocode.ping.infrastructure.persistence.PingRepositoryAdapter
|
|
||||||
import at.mocode.ping.infrastructure.web.PingController
|
|
||||||
import at.mocode.ping.test.TestPingServiceApplication
|
|
||||||
import io.mockk.every
|
|
||||||
import io.mockk.mockk
|
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
|
||||||
import org.junit.jupiter.api.Test
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired
|
|
||||||
import org.springframework.beans.factory.annotation.Qualifier
|
|
||||||
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
|
|
||||||
import org.springframework.boot.test.context.TestConfiguration
|
|
||||||
import org.springframework.context.annotation.Bean
|
|
||||||
import org.springframework.context.annotation.Import
|
|
||||||
import org.springframework.context.annotation.Primary
|
|
||||||
import org.springframework.test.context.ActiveProfiles
|
|
||||||
import org.springframework.test.context.ContextConfiguration
|
|
||||||
import org.springframework.test.web.servlet.MockMvc
|
|
||||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
|
|
||||||
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
|
|
||||||
import java.time.Instant
|
|
||||||
import kotlin.uuid.ExperimentalUuidApi
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lightweight Spring MVC integration test (no full application context / datasource).
|
|
||||||
*/
|
|
||||||
@WebMvcTest(
|
|
||||||
controllers = [PingController::class],
|
|
||||||
properties = ["spring.aop.proxy-target-class=true"]
|
|
||||||
)
|
|
||||||
@ContextConfiguration(classes = [TestPingServiceApplication::class])
|
|
||||||
@ActiveProfiles("test")
|
|
||||||
@Import(PingControllerIntegrationTest.PingControllerIntegrationTestConfig::class)
|
|
||||||
@OptIn(ExperimentalUuidApi::class)
|
|
||||||
class PingControllerIntegrationTest {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private lateinit var mockMvc: MockMvc
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
@Qualifier("pingUseCaseIntegrationMock")
|
|
||||||
private lateinit var pingUseCase: PingUseCase
|
|
||||||
|
|
||||||
@TestConfiguration
|
|
||||||
class PingControllerIntegrationTestConfig {
|
|
||||||
@Bean("pingUseCaseIntegrationMock")
|
|
||||||
@Primary
|
|
||||||
fun pingUseCase(): PingUseCase = mockk(relaxed = true)
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@Primary
|
|
||||||
fun pingRepositoryAdapter(): PingRepositoryAdapter = mockk(relaxed = true)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `should start MVC slice and serve endpoints`() {
|
|
||||||
// Just verify the MVC wiring starts and endpoints respond 200
|
|
||||||
mockMvc.perform(get("/ping/health")).andExpect(status().isOk)
|
|
||||||
|
|
||||||
// For endpoints that require the use-case, the relaxed mock is sufficient,
|
|
||||||
// but we still provide deterministic ping data.
|
|
||||||
every { pingUseCase.executePing(any()) } returns Ping(
|
|
||||||
message = "Simple Ping",
|
|
||||||
timestamp = Instant.parse("2023-10-01T10:00:00Z")
|
|
||||||
)
|
|
||||||
|
|
||||||
// Note: we don't assert the full JSON here (covered by PingControllerTest)
|
|
||||||
val result = mockMvc.perform(get("/ping/simple")).andReturn()
|
|
||||||
assertThat(result.response.status).isEqualTo(200)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-56
@@ -1,56 +0,0 @@
|
|||||||
package at.mocode.ping.service
|
|
||||||
|
|
||||||
import at.mocode.ping.application.PingService
|
|
||||||
import at.mocode.ping.domain.Ping
|
|
||||||
import at.mocode.ping.domain.PingRepository
|
|
||||||
import io.mockk.every
|
|
||||||
import io.mockk.mockk
|
|
||||||
import io.mockk.verify
|
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
|
||||||
import org.junit.jupiter.api.Test
|
|
||||||
import kotlin.uuid.ExperimentalUuidApi
|
|
||||||
import kotlin.uuid.Uuid
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unit tests for the actual application service (`PingService`).
|
|
||||||
*
|
|
||||||
* The previous `PingServiceCircuitBreakerTest` referenced an outdated component.
|
|
||||||
*/
|
|
||||||
@OptIn(ExperimentalUuidApi::class)
|
|
||||||
class PingServiceCircuitBreakerTest {
|
|
||||||
|
|
||||||
private val repository: PingRepository = mockk()
|
|
||||||
private val service = PingService(repository)
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `executePing should persist and return ping`() {
|
|
||||||
every { repository.save(any()) } answers { firstArg() }
|
|
||||||
|
|
||||||
val result = service.executePing("Hello")
|
|
||||||
|
|
||||||
assertThat(result.message).isEqualTo("Hello")
|
|
||||||
verify { repository.save(any()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `getPingHistory should delegate to repository`() {
|
|
||||||
every { repository.findAll() } returns emptyList()
|
|
||||||
|
|
||||||
val result = service.getPingHistory()
|
|
||||||
|
|
||||||
assertThat(result).isEmpty()
|
|
||||||
verify { repository.findAll() }
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `getPing should delegate to repository`() {
|
|
||||||
val id = Uuid.generateV7()
|
|
||||||
val ping = Ping(id = id, message = "Hi")
|
|
||||||
every { repository.findById(id) } returns ping
|
|
||||||
|
|
||||||
val result = service.getPing(id)
|
|
||||||
|
|
||||||
assertThat(result).isEqualTo(ping)
|
|
||||||
verify { repository.findById(id) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+2
-1
@@ -1,5 +1,6 @@
|
|||||||
package at.mocode.ping.test
|
package at.mocode.ping.test
|
||||||
|
|
||||||
|
import at.mocode.ping.infrastructure.PingProperties
|
||||||
import at.mocode.ping.infrastructure.web.PingController
|
import at.mocode.ping.infrastructure.web.PingController
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication
|
import org.springframework.boot.autoconfigure.SpringBootApplication
|
||||||
import org.springframework.context.annotation.ComponentScan
|
import org.springframework.context.annotation.ComponentScan
|
||||||
@@ -21,6 +22,6 @@ import org.springframework.context.annotation.Import
|
|||||||
@ComponentScan(
|
@ComponentScan(
|
||||||
basePackages = ["at.mocode.infrastructure.security"]
|
basePackages = ["at.mocode.infrastructure.security"]
|
||||||
)
|
)
|
||||||
@Import(PingController::class)
|
@Import(PingController::class, PingProperties::class)
|
||||||
@EnableAspectJAutoProxy(proxyTargetClass = true) // Erzwingt CGLIB Proxies für Controller
|
@EnableAspectJAutoProxy(proxyTargetClass = true) // Erzwingt CGLIB Proxies für Controller
|
||||||
class TestPingServiceApplication
|
class TestPingServiceApplication
|
||||||
|
|||||||
Reference in New Issue
Block a user