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 {
// Contract endpoints
@GetMapping("/api/ping/simple")
@GetMapping("/ping/simple")
override suspend fun simplePing(): PingResponse {
val now = OffsetDateTime.now().format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)
return PingResponse(
@@ -30,11 +30,11 @@ class PingController(
)
}
@GetMapping("/api/ping/enhanced")
@GetMapping("/ping/enhanced")
override suspend fun enhancedPing(
@RequestParam(required = false, defaultValue = "false") simulate: Boolean
): EnhancedPingResponse = pingService.ping(simulate)
@GetMapping("/api/ping/health")
@GetMapping("/ping/health")
override suspend fun healthCheck(): HealthResponse = pingService.healthCheck()
}
@@ -46,7 +46,7 @@ class PingControllerIntegrationTest {
@Test
fun `should return basic ping response from standard endpoint`() {
// When
val response = restTemplate.getForEntity(getUrl("/ping"), Map::class.java)
val response = restTemplate.getForEntity(getUrl("/ping/simple"), Map::class.java)
// Then
assertThat(response.statusCode).isEqualTo(HttpStatus.OK)
@@ -71,7 +71,7 @@ class PingControllerIntegrationTest {
val body = response.body!!
assertThat(body["status"]).isEqualTo("pong")
assertThat(body["service"]).isEqualTo("ping-service")
assertThat(body["circuitBreaker"]).isEqualTo("CLOSED")
assertThat(body["circuitBreakerState"]).isEqualTo("CLOSED")
assertThat(body["timestamp"]).isNotNull()
logger.info("Enhanced ping response: {}", body)
@@ -89,7 +89,7 @@ class PingControllerIntegrationTest {
val body = response.body!!
assertThat(body["status"]).isEqualTo("pong")
assertThat(body["service"]).isEqualTo("ping-service")
assertThat(body["circuitBreaker"]).isEqualTo("CLOSED")
assertThat(body["circuitBreakerState"]).isEqualTo("CLOSED")
logger.info("Enhanced ping without simulation: {}", body)
}
@@ -125,8 +125,9 @@ class PingControllerIntegrationTest {
assertThat(response.body).isNotNull
val body = response.body!!
assertThat(body["status"]).isEqualTo("UP")
assertThat(body["circuitBreaker"]).isEqualTo("CLOSED")
assertThat(body["status"]).isEqualTo("pong")
assertThat(body["service"]).isEqualTo("ping-service")
assertThat(body["healthy"]).isEqualTo(true)
assertThat(body["timestamp"]).isNotNull()
logger.info("Health check response: {}", body)
@@ -146,39 +147,13 @@ class PingControllerIntegrationTest {
assertThat(response.body).isNotNull
val body = response.body!!
assertThat(body["status"]).isEqualTo("DOWN")
assertThat(body["circuitBreaker"]).isEqualTo("OPEN")
assertThat(body["message"]).isEqualTo("Health check temporarily unavailable")
assertThat(body["status"]).isEqualTo("down")
assertThat(body["service"]).isEqualTo("ping-service")
assertThat(body["healthy"]).isEqualTo(false)
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
fun `should handle multiple rapid requests correctly`() {
@@ -219,7 +194,7 @@ class PingControllerIntegrationTest {
// All should return fallback responses while circuit breaker is open
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"])
}
@@ -228,12 +203,11 @@ class PingControllerIntegrationTest {
}
@Test
fun `should test all endpoints return valid responses`() {
fun `should test all existing endpoints return valid responses`() {
val endpoints = listOf(
"/ping",
"/ping/simple",
"/ping/enhanced",
"/ping/health",
"/ping/test-failure"
"/ping/health"
)
endpoints.forEach { endpoint ->
@@ -1,7 +1,11 @@
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.github.resilience4j.circuitbreaker.CircuitBreakerRegistry
import io.mockk.verify
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
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.result.MockMvcResultMatchers.*
@WebMvcTest(LegacyPingController::class)
/**
* Unit tests for PingController
* Tests REST endpoints with mocked dependencies
*/
@WebMvcTest(PingController::class)
@Import(PingControllerTest.TestConfig::class)
class PingControllerTest {
@Autowired
private lateinit var mockMvc: MockMvc
@Autowired
private lateinit var pingService: PingServiceCircuitBreaker
@TestConfiguration
class TestConfig {
@Bean
fun pingServiceCircuitBreaker(): PingServiceCircuitBreaker = mockk()
fun pingServiceCircuitBreaker(): PingServiceCircuitBreaker = mockk(relaxed = true)
}
@Bean
fun circuitBreakerRegistry(): CircuitBreakerRegistry = mockk()
@BeforeEach
fun setUp() {
// Reset mocks before each test
io.mockk.clearMocks(pingService)
}
@Test
fun `ping endpoint should return pong status`() {
mockMvc.perform(get("/ping"))
fun `should return simple ping response`() {
// When & Then
mockMvc.perform(get("/ping/simple"))
.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) }
}
}