feat(ingress): 实现 JDBC 信箱轮询、入站持久化与对拍测试 (U05)

This commit is contained in:
windyboy
2026-09-07 15:11:33 +08:00
parent dc68f1e1f8
commit c7b4b527ef
51 changed files with 1005 additions and 235 deletions
@@ -1,15 +1,15 @@
package com.gzzn.omms.msgexchange.nextgen
package com.gzzn.omms.msgexchange
import com.gzzn.omms.msgexchange.nextgen.delivery.Dispatcher
import com.gzzn.omms.msgexchange.nextgen.domain.ErrorClass
import com.gzzn.omms.msgexchange.nextgen.domain.MsgEvent
import com.gzzn.omms.msgexchange.nextgen.domain.ProcStatus
import com.gzzn.omms.msgexchange.nextgen.domain.Targets
import com.gzzn.omms.msgexchange.nextgen.infra.stub.StubDeliveryPort
import com.gzzn.omms.msgexchange.nextgen.infra.stub.StubMsgEvents
import com.gzzn.omms.msgexchange.nextgen.infra.stub.StubProcState
import com.gzzn.omms.msgexchange.nextgen.ingress.InboxController
import com.gzzn.omms.msgexchange.nextgen.processing.Pump
import com.gzzn.omms.msgexchange.delivery.Dispatcher
import com.gzzn.omms.msgexchange.domain.ErrorClass
import com.gzzn.omms.msgexchange.domain.MsgEvent
import com.gzzn.omms.msgexchange.domain.ProcStatus
import com.gzzn.omms.msgexchange.domain.Targets
import com.gzzn.omms.msgexchange.infra.stub.StubDeliveryPort
import com.gzzn.omms.msgexchange.infra.stub.StubMsgEvents
import com.gzzn.omms.msgexchange.infra.stub.StubProcState
import com.gzzn.omms.msgexchange.ingress.InboxController
import com.gzzn.omms.msgexchange.processing.Pump
import io.micronaut.context.ApplicationContext
import io.micronaut.test.extensions.junit5.annotation.MicronautTest
import jakarta.inject.Inject
@@ -22,7 +22,7 @@ import org.junit.jupiter.api.Test
* U07 端到端stub 装配等价 /beans 核验 MySQL/Redis/Kafka
* 核心 bean 装配齐全compat HTTP PG 入队主泵领取解码未实装FAILED(CODEC_ERROR)+退避
* U11 重放把 FAILED 拉回 PENDINGDispatcher flushSchd 聚合发出 schd
* 生产主路径 JDBC 轮询 InboxPoller U05本测未覆盖
* 生产主路径 JDBC 轮询 InboxPollercompat HTTP 写路径见 InboxService
* 后台循环关闭autostart=false按需手动 tick避免测试泄漏线程
*/
@MicronautTest
@@ -44,14 +44,14 @@ class PipelineSmokeTest {
@org.junit.jupiter.api.BeforeEach
fun cleanStubs() {
ctx.getBean(StubProcState::class.java).clear()
ctx.getBean(com.gzzn.omms.msgexchange.nextgen.infra.stub.StubInbox::class.java).clear()
ctx.getBean(com.gzzn.omms.msgexchange.infra.stub.StubInbox::class.java).clear()
ctx.getBean(StubMsgEvents::class.java).clear()
ctx.getBean(StubDeliveryPort::class.java).clear()
}
@Test
fun `core pipeline beans are wired under stubs`() {
assertNotNull(ctx.getBean(com.gzzn.omms.msgexchange.nextgen.config.PipelineProps::class.java))
assertNotNull(ctx.getBean(com.gzzn.omms.msgexchange.config.PipelineProps::class.java))
assertTrue(ctx.getBeansOfType(Pump::class.java).isNotEmpty())
assertTrue(ctx.getBeansOfType(Dispatcher::class.java).isNotEmpty())
assertNotNull(controller)
@@ -82,7 +82,7 @@ class PipelineSmokeTest {
val stub = ctx.getBean(StubProcState::class.java)
assertEquals(ProcStatus.FAILED, stub.snapshotOf(id)!!.state)
val n = ctx.getBean(com.gzzn.omms.msgexchange.nextgen.infra.retry.ReplayService::class.java)
val n = ctx.getBean(com.gzzn.omms.msgexchange.infra.retry.ReplayService::class.java)
.replay(listOf(ErrorClass.CODEC_ERROR))
assertEquals(1, n)
@@ -1,4 +1,4 @@
package com.gzzn.omms.msgexchange.nextgen
package com.gzzn.omms.msgexchange
import java.time.Clock
import java.time.Instant
@@ -1,4 +1,4 @@
package com.gzzn.omms.msgexchange.nextgen.config
package com.gzzn.omms.msgexchange.config
import io.micronaut.test.extensions.junit5.annotation.MicronautTest
import jakarta.inject.Inject
@@ -1,4 +1,4 @@
package com.gzzn.omms.msgexchange.nextgen.config
package com.gzzn.omms.msgexchange.config
import io.micronaut.test.extensions.junit5.annotation.MicronautTest
import io.micronaut.test.support.TestPropertyProvider
@@ -1,4 +1,4 @@
package com.gzzn.omms.msgexchange.nextgen.config
package com.gzzn.omms.msgexchange.config
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
@@ -1,13 +1,13 @@
package com.gzzn.omms.msgexchange.nextgen.delivery
package com.gzzn.omms.msgexchange.delivery
import com.gzzn.omms.msgexchange.nextgen.MutableClock
import com.gzzn.omms.msgexchange.nextgen.config.PipelineProps
import com.gzzn.omms.msgexchange.nextgen.domain.ErrorClass
import com.gzzn.omms.msgexchange.nextgen.domain.EventStatus
import com.gzzn.omms.msgexchange.nextgen.domain.MsgEvent
import com.gzzn.omms.msgexchange.nextgen.domain.Targets
import com.gzzn.omms.msgexchange.nextgen.infra.persistence.MsgEventRepository
import com.gzzn.omms.msgexchange.nextgen.infra.retry.FailureScheduler
import com.gzzn.omms.msgexchange.MutableClock
import com.gzzn.omms.msgexchange.config.PipelineProps
import com.gzzn.omms.msgexchange.domain.ErrorClass
import com.gzzn.omms.msgexchange.domain.EventStatus
import com.gzzn.omms.msgexchange.domain.MsgEvent
import com.gzzn.omms.msgexchange.domain.Targets
import com.gzzn.omms.msgexchange.infra.persistence.MsgEventRepository
import com.gzzn.omms.msgexchange.infra.retry.FailureScheduler
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.Assertions.assertNull
@@ -1,8 +1,8 @@
package com.gzzn.omms.msgexchange.nextgen.delivery
package com.gzzn.omms.msgexchange.delivery
import com.gzzn.omms.msgexchange.nextgen.domain.EventStatus
import com.gzzn.omms.msgexchange.nextgen.domain.MsgEvent
import com.gzzn.omms.msgexchange.nextgen.domain.Targets
import com.gzzn.omms.msgexchange.domain.EventStatus
import com.gzzn.omms.msgexchange.domain.MsgEvent
import com.gzzn.omms.msgexchange.domain.Targets
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals
@@ -1,8 +1,8 @@
package com.gzzn.omms.msgexchange.nextgen.infra.health
package com.gzzn.omms.msgexchange.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 com.gzzn.omms.msgexchange.delivery.DeliveryPort
import com.gzzn.omms.msgexchange.infra.redis.FlightRedisClient
import com.gzzn.omms.msgexchange.infra.redis.RedisScript
import io.micronaut.health.HealthStatus
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
@@ -1,9 +1,9 @@
package com.gzzn.omms.msgexchange.nextgen.infra.retry
package com.gzzn.omms.msgexchange.infra.retry
import com.gzzn.omms.msgexchange.nextgen.domain.ErrorClass
import com.gzzn.omms.msgexchange.nextgen.domain.ProcState
import com.gzzn.omms.msgexchange.nextgen.domain.ProcStatus
import com.gzzn.omms.msgexchange.nextgen.infra.persistence.ProcStateRepository
import com.gzzn.omms.msgexchange.domain.ErrorClass
import com.gzzn.omms.msgexchange.domain.ProcState
import com.gzzn.omms.msgexchange.domain.ProcStatus
import com.gzzn.omms.msgexchange.infra.persistence.ProcStateRepository
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Test
@@ -24,6 +24,7 @@ class ReplayServiceTest {
}
override fun insert(cminmsgsId: Long, state: ProcStatus) = Unit
override fun exists(cminmsgsId: Long): Boolean = rows.containsKey(cminmsgsId)
override fun headUnfinished(): ProcState? = null
override fun tryBindIdentity(cminmsgsId: Long, identityKey: String): Boolean = true
override fun ownerOfIdentity(identityKey: String): Long? = null
@@ -0,0 +1,45 @@
package com.gzzn.omms.msgexchange.ingress
import com.gzzn.omms.msgexchange.domain.ProcStatus
import com.gzzn.omms.msgexchange.infra.stub.StubInbox
import com.gzzn.omms.msgexchange.infra.stub.StubProcState
import com.gzzn.omms.msgexchange.processing.Pump
import io.micronaut.test.extensions.junit5.annotation.MicronautTest
import jakarta.inject.Inject
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
/** 主路径:JDBC 轮询语义(stub 下 InboxPoller + 外部写信箱模拟)。 */
@MicronautTest
class InboxPollerTest {
@Inject lateinit var poller: InboxPoller
@Inject lateinit var pump: Pump
@Inject lateinit var stubInbox: StubInbox
@Inject lateinit var stubProc: StubProcState
@BeforeEach
fun clean() {
stubInbox.clear()
stubProc.clear()
}
@Test
fun `poller enqueues externally written mailbox rows`() {
val id = stubInbox.simulateExternalWrite("<MSG/>")
assertEquals(1, poller.pollOnce())
assertNotNull(stubProc.snapshotOf(id))
assertEquals(ProcStatus.PENDING, stubProc.snapshotOf(id)!!.state)
}
@Test
fun `poller is idempotent for already enqueued rows`() {
val id = stubInbox.simulateExternalWrite("<MSG/>")
poller.pollOnce()
assertEquals(0, poller.pollOnce())
pump.tick()
assertEquals(ProcStatus.FAILED, stubProc.snapshotOf(id)!!.state)
}
}
@@ -1,9 +1,9 @@
package com.gzzn.omms.msgexchange.nextgen.processing
package com.gzzn.omms.msgexchange.processing
import com.gzzn.omms.msgexchange.nextgen.config.PipelineProps
import com.gzzn.omms.msgexchange.nextgen.domain.DecodedMessage
import com.gzzn.omms.msgexchange.nextgen.domain.MetaFields
import com.gzzn.omms.msgexchange.nextgen.domain.MsgKind
import com.gzzn.omms.msgexchange.config.PipelineProps
import com.gzzn.omms.msgexchange.domain.DecodedMessage
import com.gzzn.omms.msgexchange.domain.MetaFields
import com.gzzn.omms.msgexchange.domain.MsgKind
import org.junit.jupiter.api.Test
import java.time.LocalDate
import kotlin.test.assertEquals
@@ -1,33 +1,33 @@
package com.gzzn.omms.msgexchange.nextgen.processing
package com.gzzn.omms.msgexchange.processing
import com.gzzn.omms.msgexchange.nextgen.MutableClock
import com.gzzn.omms.msgexchange.nextgen.codec.DecodeFailure
import com.gzzn.omms.msgexchange.nextgen.codec.DecodeResult
import com.gzzn.omms.msgexchange.nextgen.codec.XmlCodec
import com.gzzn.omms.msgexchange.nextgen.config.PipelineProps
import com.gzzn.omms.msgexchange.nextgen.domain.DecodedMessage
import com.gzzn.omms.msgexchange.nextgen.domain.Decision
import com.gzzn.omms.msgexchange.nextgen.domain.ErrorClass
import com.gzzn.omms.msgexchange.nextgen.domain.EventStatus
import com.gzzn.omms.msgexchange.nextgen.domain.MsgEvent
import com.gzzn.omms.msgexchange.nextgen.domain.MsgKind
import com.gzzn.omms.msgexchange.nextgen.domain.MetaFields
import com.gzzn.omms.msgexchange.nextgen.domain.NotifyPayload
import com.gzzn.omms.msgexchange.nextgen.domain.ProcState
import com.gzzn.omms.msgexchange.nextgen.domain.ProcStatus
import com.gzzn.omms.msgexchange.nextgen.domain.SchdPush
import com.gzzn.omms.msgexchange.nextgen.domain.Targets
import com.gzzn.omms.msgexchange.nextgen.infra.persistence.CminmsgInboxRepository
import com.gzzn.omms.msgexchange.nextgen.infra.persistence.MsgEventRepository
import com.gzzn.omms.msgexchange.nextgen.infra.persistence.ProcStateRepository
import com.gzzn.omms.msgexchange.nextgen.infra.persistence.PumpJobRepository
import com.gzzn.omms.msgexchange.nextgen.infra.persistence.RefDataRepository
import com.gzzn.omms.msgexchange.nextgen.infra.persistence.ReqTrackRepository
import com.gzzn.omms.msgexchange.nextgen.infra.persistence.FlightStateRepository
import com.gzzn.omms.msgexchange.nextgen.infra.redis.FlightRedisClient
import com.gzzn.omms.msgexchange.nextgen.infra.redis.RedisScript
import com.gzzn.omms.msgexchange.nextgen.infra.retry.FailureScheduler
import com.gzzn.omms.msgexchange.nextgen.infra.retry.ProcFailure
import com.gzzn.omms.msgexchange.MutableClock
import com.gzzn.omms.msgexchange.codec.DecodeFailure
import com.gzzn.omms.msgexchange.codec.DecodeResult
import com.gzzn.omms.msgexchange.codec.XmlCodec
import com.gzzn.omms.msgexchange.config.PipelineProps
import com.gzzn.omms.msgexchange.domain.DecodedMessage
import com.gzzn.omms.msgexchange.domain.Decision
import com.gzzn.omms.msgexchange.domain.ErrorClass
import com.gzzn.omms.msgexchange.domain.EventStatus
import com.gzzn.omms.msgexchange.domain.MsgEvent
import com.gzzn.omms.msgexchange.domain.MsgKind
import com.gzzn.omms.msgexchange.domain.MetaFields
import com.gzzn.omms.msgexchange.domain.NotifyPayload
import com.gzzn.omms.msgexchange.domain.ProcState
import com.gzzn.omms.msgexchange.domain.ProcStatus
import com.gzzn.omms.msgexchange.domain.SchdPush
import com.gzzn.omms.msgexchange.domain.Targets
import com.gzzn.omms.msgexchange.infra.persistence.CminmsgInboxRepository
import com.gzzn.omms.msgexchange.infra.persistence.MsgEventRepository
import com.gzzn.omms.msgexchange.infra.persistence.ProcStateRepository
import com.gzzn.omms.msgexchange.infra.persistence.PumpJobRepository
import com.gzzn.omms.msgexchange.infra.persistence.RefDataRepository
import com.gzzn.omms.msgexchange.infra.persistence.ReqTrackRepository
import com.gzzn.omms.msgexchange.infra.persistence.FlightStateRepository
import com.gzzn.omms.msgexchange.infra.redis.FlightRedisClient
import com.gzzn.omms.msgexchange.infra.redis.RedisScript
import com.gzzn.omms.msgexchange.infra.retry.FailureScheduler
import com.gzzn.omms.msgexchange.infra.retry.ProcFailure
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.Assertions.assertNull
@@ -50,6 +50,8 @@ class MessageProcessorTest {
override fun insert(cminmsgsId: Long, state: ProcStatus) { record[cminmsgsId] = ProcState(cminmsgsId, state) }
override fun exists(cminmsgsId: Long): Boolean = record.containsKey(cminmsgsId)
override fun headUnfinished(): ProcState? = null
override fun tryBindIdentity(cminmsgsId: Long, identityKey: String): Boolean {
@@ -99,6 +101,8 @@ class MessageProcessorTest {
override fun insertRaw(rawXml: String): Long = 0
override fun pollUnprocessed(afterId: Long, limit: Int): List<Long> = emptyList()
override fun rawOf(cminmsgsId: Long): String? {
throwOnRawOf?.let { throw it }
return raws[cminmsgsId]