fixing ping-service

This commit is contained in:
2025-09-24 23:11:29 +02:00
parent 1c4184809a
commit b8c008ddba
4 changed files with 117 additions and 138 deletions
@@ -1,87 +0,0 @@
package at.mocode.ping.service
import at.mocode.ping.api.PingResponse
import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry
import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*
import java.time.OffsetDateTime
import java.time.format.DateTimeFormatter
import java.util.UUID
@RestController
@CrossOrigin(
origins = ["http://localhost:8080", "http://localhost:8083", "http://localhost:4000"],
methods = [RequestMethod.GET, RequestMethod.POST, RequestMethod.PUT, RequestMethod.DELETE, RequestMethod.OPTIONS],
allowedHeaders = ["*"],
allowCredentials = "true"
)
class LegacyPingController(
private val pingService: PingServiceCircuitBreaker,
private val circuitBreakerRegistry: CircuitBreakerRegistry
) {
@GetMapping("/ping", produces = [MediaType.APPLICATION_JSON_VALUE])
fun legacySimplePing(): ResponseEntity<PingResponse> {
val now = OffsetDateTime.now().format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)
val resp = PingResponse(
status = "pong",
timestamp = now,
service = "ping-service"
)
return ResponseEntity
.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(resp)
}
@GetMapping("/ping/enhanced")
fun legacyEnhanced(@RequestParam(required = false, defaultValue = "false") simulate: Boolean): Map<String, Any> {
val dto = pingService.ping(simulate)
val map = mutableMapOf<String, Any>(
"status" to dto.status,
"timestamp" to dto.timestamp,
"service" to dto.service,
"circuitBreaker" to dto.circuitBreakerState
)
if (dto.status.equals("fallback", ignoreCase = true) || dto.service.contains("fallback")) {
map["message"] = "Service temporarily unavailable"
map["error"] = UUID.randomUUID().toString()
}
return map
}
@GetMapping("/ping/health")
fun legacyHealth(): Map<String, Any> {
val dto = pingService.healthCheck()
val state = circuitBreakerRegistry
.circuitBreaker(PingServiceCircuitBreaker.PING_CIRCUIT_BREAKER)
.state
val cb = if (state.name == "OPEN") "OPEN" else "CLOSED"
val map = mutableMapOf<String, Any>(
"status" to if (dto.healthy) "UP" else "DOWN",
"timestamp" to dto.timestamp,
"circuitBreaker" to cb
)
if (!dto.healthy) {
map["message"] = "Health check temporarily unavailable"
}
return map
}
@GetMapping("/ping/test-failure")
fun legacyTestFailure(): Map<String, Any> {
val dto = pingService.ping(simulateFailure = true)
val map = mutableMapOf<String, Any>(
"status" to dto.status,
"timestamp" to dto.timestamp,
"service" to dto.service,
"circuitBreaker" to dto.circuitBreakerState
)
if (dto.status.equals("fallback", ignoreCase = true) || dto.service.contains("fallback")) {
map["message"] = "Service temporarily unavailable"
map["error"] = UUID.randomUUID().toString()
}
return map
}
}
@@ -20,7 +20,7 @@ class PingController(
) : PingApi { ) : PingApi {
// Contract endpoints // Contract endpoints
@GetMapping("/api/ping/simple") @GetMapping("/ping/simple")
override suspend fun simplePing(): PingResponse { override suspend fun simplePing(): PingResponse {
val now = OffsetDateTime.now().format(DateTimeFormatter.ISO_OFFSET_DATE_TIME) val now = OffsetDateTime.now().format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)
return PingResponse( return PingResponse(
@@ -30,11 +30,11 @@ class PingController(
) )
} }
@GetMapping("/api/ping/enhanced") @GetMapping("/ping/enhanced")
override suspend fun enhancedPing( override suspend fun enhancedPing(
@RequestParam(required = false, defaultValue = "false") simulate: Boolean @RequestParam(required = false, defaultValue = "false") simulate: Boolean
): EnhancedPingResponse = pingService.ping(simulate) ): EnhancedPingResponse = pingService.ping(simulate)
@GetMapping("/api/ping/health") @GetMapping("/ping/health")
override suspend fun healthCheck(): HealthResponse = pingService.healthCheck() override suspend fun healthCheck(): HealthResponse = pingService.healthCheck()
} }
@@ -46,7 +46,7 @@ class PingControllerIntegrationTest {
@Test @Test
fun `should return basic ping response from standard endpoint`() { fun `should return basic ping response from standard endpoint`() {
// When // When
val response = restTemplate.getForEntity(getUrl("/ping"), Map::class.java) val response = restTemplate.getForEntity(getUrl("/ping/simple"), Map::class.java)
// Then // Then
assertThat(response.statusCode).isEqualTo(HttpStatus.OK) assertThat(response.statusCode).isEqualTo(HttpStatus.OK)
@@ -71,7 +71,7 @@ class PingControllerIntegrationTest {
val body = response.body!! val body = response.body!!
assertThat(body["status"]).isEqualTo("pong") assertThat(body["status"]).isEqualTo("pong")
assertThat(body["service"]).isEqualTo("ping-service") assertThat(body["service"]).isEqualTo("ping-service")
assertThat(body["circuitBreaker"]).isEqualTo("CLOSED") assertThat(body["circuitBreakerState"]).isEqualTo("CLOSED")
assertThat(body["timestamp"]).isNotNull() assertThat(body["timestamp"]).isNotNull()
logger.info("Enhanced ping response: {}", body) logger.info("Enhanced ping response: {}", body)
@@ -89,7 +89,7 @@ class PingControllerIntegrationTest {
val body = response.body!! val body = response.body!!
assertThat(body["status"]).isEqualTo("pong") assertThat(body["status"]).isEqualTo("pong")
assertThat(body["service"]).isEqualTo("ping-service") assertThat(body["service"]).isEqualTo("ping-service")
assertThat(body["circuitBreaker"]).isEqualTo("CLOSED") assertThat(body["circuitBreakerState"]).isEqualTo("CLOSED")
logger.info("Enhanced ping without simulation: {}", body) logger.info("Enhanced ping without simulation: {}", body)
} }
@@ -125,8 +125,9 @@ class PingControllerIntegrationTest {
assertThat(response.body).isNotNull assertThat(response.body).isNotNull
val body = response.body!! val body = response.body!!
assertThat(body["status"]).isEqualTo("UP") assertThat(body["status"]).isEqualTo("pong")
assertThat(body["circuitBreaker"]).isEqualTo("CLOSED") assertThat(body["service"]).isEqualTo("ping-service")
assertThat(body["healthy"]).isEqualTo(true)
assertThat(body["timestamp"]).isNotNull() assertThat(body["timestamp"]).isNotNull()
logger.info("Health check response: {}", body) logger.info("Health check response: {}", body)
@@ -146,39 +147,13 @@ class PingControllerIntegrationTest {
assertThat(response.body).isNotNull assertThat(response.body).isNotNull
val body = response.body!! val body = response.body!!
assertThat(body["status"]).isEqualTo("DOWN") assertThat(body["status"]).isEqualTo("down")
assertThat(body["circuitBreaker"]).isEqualTo("OPEN") assertThat(body["service"]).isEqualTo("ping-service")
assertThat(body["message"]).isEqualTo("Health check temporarily unavailable") assertThat(body["healthy"]).isEqualTo(false)
logger.info("Fallback health check response: {}", body) logger.info("Fallback health check response: {}", body)
} }
@Test
fun `should return response from test-failure endpoint`() {
// When
val response = restTemplate.getForEntity(getUrl("/ping/test-failure"), Map::class.java)
// Then
assertThat(response.statusCode).isEqualTo(HttpStatus.OK)
assertThat(response.body).isNotNull
val body = response.body!!
// Due to 60% failure simulation, we expect either success or fallback
assertThat(body["status"]).isIn("pong", "fallback")
if (body["status"] == "fallback") {
assertThat(body["service"]).isEqualTo("ping-service-fallback")
assertThat(body["circuitBreaker"]).isEqualTo("OPEN")
assertThat(body["message"]).isEqualTo("Service temporarily unavailable")
assertThat(body["error"]).isNotNull()
} else {
assertThat(body["service"]).isEqualTo("ping-service")
assertThat(body["circuitBreaker"]).isEqualTo("CLOSED")
}
logger.info("Test failure endpoint response: {}", body)
}
@Test @Test
fun `should handle multiple rapid requests correctly`() { fun `should handle multiple rapid requests correctly`() {
@@ -219,7 +194,7 @@ class PingControllerIntegrationTest {
// All should return fallback responses while circuit breaker is open // All should return fallback responses while circuit breaker is open
assertThat(body["status"]).isEqualTo("fallback") assertThat(body["status"]).isEqualTo("fallback")
assertThat(body["circuitBreaker"]).isEqualTo("OPEN") assertThat(body["circuitBreakerState"]).isEqualTo("OPEN")
logger.info("Request {} with OPEN circuit breaker: {}", i + 1, body["status"]) logger.info("Request {} with OPEN circuit breaker: {}", i + 1, body["status"])
} }
@@ -228,12 +203,11 @@ class PingControllerIntegrationTest {
} }
@Test @Test
fun `should test all endpoints return valid responses`() { fun `should test all existing endpoints return valid responses`() {
val endpoints = listOf( val endpoints = listOf(
"/ping", "/ping/simple",
"/ping/enhanced", "/ping/enhanced",
"/ping/health", "/ping/health"
"/ping/test-failure"
) )
endpoints.forEach { endpoint -> endpoints.forEach { endpoint ->
@@ -1,7 +1,11 @@
package at.mocode.ping.service package at.mocode.ping.service
import at.mocode.ping.api.EnhancedPingResponse
import at.mocode.ping.api.HealthResponse
import io.mockk.every
import io.mockk.mockk import io.mockk.mockk
import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry import io.mockk.verify
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
@@ -12,27 +16,115 @@ import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.* import org.springframework.test.web.servlet.result.MockMvcResultMatchers.*
@WebMvcTest(LegacyPingController::class) /**
* Unit tests for PingController
* Tests REST endpoints with mocked dependencies
*/
@WebMvcTest(PingController::class)
@Import(PingControllerTest.TestConfig::class) @Import(PingControllerTest.TestConfig::class)
class PingControllerTest { class PingControllerTest {
@Autowired @Autowired
private lateinit var mockMvc: MockMvc private lateinit var mockMvc: MockMvc
@Autowired
private lateinit var pingService: PingServiceCircuitBreaker
@TestConfiguration @TestConfiguration
class TestConfig { class TestConfig {
@Bean @Bean
fun pingServiceCircuitBreaker(): PingServiceCircuitBreaker = mockk() fun pingServiceCircuitBreaker(): PingServiceCircuitBreaker = mockk(relaxed = true)
}
@Bean @BeforeEach
fun circuitBreakerRegistry(): CircuitBreakerRegistry = mockk() fun setUp() {
// Reset mocks before each test
io.mockk.clearMocks(pingService)
} }
@Test @Test
fun `ping endpoint should return pong status`() { fun `should return simple ping response`() {
mockMvc.perform(get("/ping")) // When & Then
mockMvc.perform(get("/ping/simple"))
.andExpect(status().isOk) .andExpect(status().isOk)
.andExpect(content().contentType("application/json")) }
.andExpect(jsonPath("$.status").value("pong"))
@Test
fun `should return enhanced ping response without simulation`() {
// Given
val expectedResponse = EnhancedPingResponse(
status = "pong",
timestamp = "2023-10-01T10:00:00Z",
service = "ping-service",
circuitBreakerState = "CLOSED",
responseTime = 10L
)
every { pingService.ping(false) } returns expectedResponse
// When & Then
mockMvc.perform(get("/ping/enhanced"))
.andExpect(status().isOk)
// Verify
verify { pingService.ping(false) }
}
@Test
fun `should return enhanced ping response with simulation enabled`() {
// Given
val expectedResponse = EnhancedPingResponse(
status = "fallback",
timestamp = "2023-10-01T10:00:00Z",
service = "ping-service-fallback",
circuitBreakerState = "OPEN",
responseTime = 5L
)
every { pingService.ping(true) } returns expectedResponse
// When & Then
mockMvc.perform(get("/ping/enhanced?simulate=true"))
.andExpect(status().isOk)
// Verify
verify { pingService.ping(true) }
}
@Test
fun `should return health check response`() {
// Given
val expectedResponse = HealthResponse(
status = "pong",
timestamp = "2023-10-01T10:00:00Z",
service = "ping-service",
healthy = true
)
every { pingService.healthCheck() } returns expectedResponse
// When & Then
mockMvc.perform(get("/ping/health"))
.andExpect(status().isOk)
// Verify
verify { pingService.healthCheck() }
}
@Test
fun `should handle missing simulate parameter with default false`() {
// Given
val expectedResponse = EnhancedPingResponse(
status = "pong",
timestamp = "2023-10-01T10:00:00Z",
service = "ping-service",
circuitBreakerState = "CLOSED",
responseTime = 8L
)
every { pingService.ping(false) } returns expectedResponse
// When & Then
mockMvc.perform(get("/ping/enhanced"))
.andExpect(status().isOk)
// Verify default parameter is used
verify { pingService.ping(false) }
} }
} }