diff --git a/services/ping/ping-service/src/main/kotlin/at/mocode/ping/service/LegacyPingController.kt b/services/ping/ping-service/src/main/kotlin/at/mocode/ping/service/LegacyPingController.kt deleted file mode 100644 index 81a161d4..00000000 --- a/services/ping/ping-service/src/main/kotlin/at/mocode/ping/service/LegacyPingController.kt +++ /dev/null @@ -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 { - 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 { - val dto = pingService.ping(simulate) - val map = mutableMapOf( - "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 { - 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( - "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 { - val dto = pingService.ping(simulateFailure = true) - val map = mutableMapOf( - "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 - } -} diff --git a/services/ping/ping-service/src/main/kotlin/at/mocode/ping/service/PingController.kt b/services/ping/ping-service/src/main/kotlin/at/mocode/ping/service/PingController.kt index a73f0837..472788e4 100644 --- a/services/ping/ping-service/src/main/kotlin/at/mocode/ping/service/PingController.kt +++ b/services/ping/ping-service/src/main/kotlin/at/mocode/ping/service/PingController.kt @@ -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() } diff --git a/services/ping/ping-service/src/test/kotlin/at/mocode/ping/service/PingControllerIntegrationTest.kt b/services/ping/ping-service/src/test/kotlin/at/mocode/ping/service/PingControllerIntegrationTest.kt index aa5aa18b..9af036fa 100644 --- a/services/ping/ping-service/src/test/kotlin/at/mocode/ping/service/PingControllerIntegrationTest.kt +++ b/services/ping/ping-service/src/test/kotlin/at/mocode/ping/service/PingControllerIntegrationTest.kt @@ -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 -> diff --git a/services/ping/ping-service/src/test/kotlin/at/mocode/ping/service/PingControllerTest.kt b/services/ping/ping-service/src/test/kotlin/at/mocode/ping/service/PingControllerTest.kt index 075dccd9..2908efb3 100644 --- a/services/ping/ping-service/src/test/kotlin/at/mocode/ping/service/PingControllerTest.kt +++ b/services/ping/ping-service/src/test/kotlin/at/mocode/ping/service/PingControllerTest.kt @@ -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) } } }