fix(delivery,health): U14 deleteOf 结构化 JSON + U12 健康检查真实 ping(ACM2-10 复审核验项②③)
- U14(复审②):deleteOf 字符串模板在 partitionKey=null/非空时均产出非法 JSON, 改 Jackson 结构化序列化(null→null 字面量,非空→带引号转义); DispatcherTickTest 新增 phase B 两样例(readTree 合法性 + refs 精确断言)。 注:该分支仅 Phase B 且 ES 投递成功后可达,当前属潜伏缺陷。 - U12(复审③):Redis/Kafka 健康指示器由仅查 bean 存在升级为真实 ping (false/异常→DOWN;缺 bean 仍 DOWN)。FlightRedisClient/DeliveryPort 增 ping() (stub 返回 true;DeliveryPort 默认实现,真实 Kafka 实装时覆写为 metadata 校验)。 ping 判定拆为可单测纯函数,HealthIndicatorsTest 覆盖 UP/DOWN/异常/缺 bean 四态。 验证:./gradlew test 37 个测试全绿(本地,未推送)。
This commit is contained in:
@@ -56,7 +56,9 @@ class DispatcherTickTest {
|
||||
attempts = attempts ?: events[i].attempts)
|
||||
}
|
||||
|
||||
override fun insertSync(events: List<MsgEvent>) = Unit
|
||||
override fun insertSync(events: List<MsgEvent>) { syncInserted += events }
|
||||
|
||||
val syncInserted = mutableListOf<MsgEvent>()
|
||||
}
|
||||
|
||||
private class FakePort : DeliveryPort {
|
||||
@@ -181,4 +183,24 @@ class DispatcherTickTest {
|
||||
assertEquals(1, repo.events.first { it.eventId == 1L }.attempts) // schd 进退避
|
||||
assertEquals(EventStatus.PENDING, repo.events.first { it.eventId == 1L }.state)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `delete events always carry legal JSON refs - null and non-null partitionKey`() {
|
||||
val props = PipelineProps().apply { phase = PipelineProps.Phase.B }
|
||||
val repo = FakeRepo()
|
||||
val port = FakePort()
|
||||
val d = dispatcher(repo, port, props)
|
||||
repo.enqueue(ev(1, Targets.ES_FLIGHT_HTS, "F1", """{"h":1}"""))
|
||||
repo.enqueue(ev(2, Targets.ES_FLIGHT_HTS, null, """{"h":2}"""))
|
||||
|
||||
d.tick(); d.tick() // 每 target 每 tick 仅出队一条:两条 ES 事件需两个 tick
|
||||
|
||||
val deletes = repo.syncInserted.filter { it.target == Targets.REDIS_FLIGHT_INFO }
|
||||
assertEquals(2, deletes.size)
|
||||
val mapper = com.fasterxml.jackson.databind.ObjectMapper()
|
||||
val parsed = deletes.map { mapper.readTree(it.payloadJson) } // readTree 即合法性断言
|
||||
assertEquals(listOf("delete", "delete"), parsed.map { it.get("op").asText() })
|
||||
assertEquals("F1", parsed[0].get("refs").asText()) // 非空 → 带引号字符串
|
||||
assertTrue(parsed[1].get("refs").isNull) // null → null 字面量
|
||||
}
|
||||
}
|
||||
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
package com.gzzn.omms.msgexchange.nextgen.infra.health
|
||||
|
||||
import com.gzzn.omms.msgexchange.nextgen.delivery.DeliveryPort
|
||||
import com.gzzn.omms.msgexchange.nextgen.infra.redis.FlightRedisClient
|
||||
import com.gzzn.omms.msgexchange.nextgen.infra.redis.RedisScript
|
||||
import io.micronaut.health.HealthStatus
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
/**
|
||||
* U12:健康指示器 UP 判据为真实 ping(复审 P1 修正)——
|
||||
* bean 缺失 / ping false / ping 抛异常 → DOWN;仅 ping 成功 → UP。
|
||||
*/
|
||||
class HealthIndicatorsTest {
|
||||
|
||||
private class FakeRedis(private val pingResult: Boolean) : FlightRedisClient {
|
||||
override fun eval(script: RedisScript, setPairs: List<Pair<String, String>>, delFields: List<String>) = Unit
|
||||
override fun hgetAllFlightInfo(): Map<String, String> = emptyMap()
|
||||
override fun ping(): Boolean = pingResult
|
||||
}
|
||||
|
||||
private class ThrowingRedis : FlightRedisClient {
|
||||
override fun eval(script: RedisScript, setPairs: List<Pair<String, String>>, delFields: List<String>) = Unit
|
||||
override fun hgetAllFlightInfo(): Map<String, String> = emptyMap()
|
||||
override fun ping(): Boolean = throw RuntimeException("connection refused")
|
||||
}
|
||||
|
||||
private class FakePort(private val pingResult: Boolean) : DeliveryPort {
|
||||
override fun sendKafka(topic: String, payloadJson: String) = Unit
|
||||
override fun indexFlightHts(payloadJson: String) = Unit
|
||||
override fun projectRedis(payloadJson: String) = Unit
|
||||
override fun ping(): Boolean = pingResult
|
||||
}
|
||||
|
||||
private class ThrowingPort : DeliveryPort {
|
||||
override fun sendKafka(topic: String, payloadJson: String) = Unit
|
||||
override fun indexFlightHts(payloadJson: String) = Unit
|
||||
override fun projectRedis(payloadJson: String) = Unit
|
||||
override fun ping(): Boolean = throw RuntimeException("metadata fetch failed")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `redis up only when ping succeeds`() {
|
||||
assertEquals(HealthStatus.UP, redisHealth(FakeRedis(true)).status)
|
||||
assertEquals(HealthStatus.DOWN, redisHealth(FakeRedis(false)).status)
|
||||
assertEquals(HealthStatus.DOWN, redisHealth(ThrowingRedis()).status)
|
||||
assertEquals(HealthStatus.DOWN, redisHealth(null).status) // bean 缺失
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `kafka up only when ping succeeds`() {
|
||||
assertEquals(HealthStatus.UP, kafkaHealth(FakePort(true)).status)
|
||||
assertEquals(HealthStatus.DOWN, kafkaHealth(FakePort(false)).status)
|
||||
assertEquals(HealthStatus.DOWN, kafkaHealth(ThrowingPort()).status)
|
||||
assertEquals(HealthStatus.DOWN, kafkaHealth(null).status) // bean 缺失
|
||||
}
|
||||
}
|
||||
@@ -138,6 +138,7 @@ class MessageProcessorTest {
|
||||
private object FakeRedis : FlightRedisClient {
|
||||
override fun eval(script: RedisScript, setPairs: List<Pair<String, String>>, delFields: List<String>) = Unit
|
||||
override fun hgetAllFlightInfo(): Map<String, String> = emptyMap()
|
||||
override fun ping(): Boolean = true
|
||||
}
|
||||
|
||||
private class FakeRefData : RefDataRepository {
|
||||
|
||||
Reference in New Issue
Block a user