fixing ping-service
This commit is contained in:
-87
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-39
@@ -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 ->
|
||||||
|
|||||||
+101
-9
@@ -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) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user