feat(runtime): U07 生命周期装配 + stub 装配冒烟(ACM2-10 U07/U01 遗留)
- PipelineLifecycle:ServerStartupEvent 后在专用单线程(msgx-pump/msgx-dispatcher,daemon) 拉起 Pump/Dispatcher 循环;停机 requestStop+interrupt+join;@Requires(msgx.pipeline.autostart=true) - PipelineProps.pipeline.autostart(默认 false,生产/测试默认不自动起循环,防泄漏线程) - stub 装配(infra/stub,@Requires(msgx.stubs=true)):7 仓储内存实装 + StubXmlCodec(未实装 → CODEC_ERROR 走 FAILED 可重放路径)+ StubRedis + StubDeliveryPort + Holder Factory - 配置:application-dev.yml(stubs+autostart,/env、/beans 放开,无需 DB 即可 dev 跑通); application-test.yml(stubs=true、autostart=false);README 补 dev stub 运行说明 - PipelineSmokeTest(4):等价 /beans 装配核验 + 收报→主泵领取→FAILED(CODEC_ERROR) + U11 重放 回 PENDING + Dispatcher flush 聚合(stub 用例前清内存状态防上下文复用串扰) - 全量测试 34 个通过
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
package com.gzzn.omms.msgexchange.nextgen
|
||||
|
||||
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 io.micronaut.context.ApplicationContext
|
||||
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.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
/**
|
||||
* U07 端到端(stub 装配,等价 /beans 核验):无 MySQL/Redis/Kafka 下,
|
||||
* ①核心 bean 装配齐全;②收报→落库→主泵领取→解码未实装→FAILED(CODEC_ERROR)+退避;
|
||||
* ③U11 重放把 FAILED 拉回 PENDING;④Dispatcher flushSchd 聚合发出 schd。
|
||||
* 后台循环关闭(autostart=false),按需手动 tick,避免测试泄漏线程。
|
||||
*/
|
||||
@MicronautTest
|
||||
class PipelineSmokeTest {
|
||||
|
||||
@Inject
|
||||
lateinit var ctx: ApplicationContext
|
||||
|
||||
@Inject
|
||||
lateinit var controller: InboxController
|
||||
|
||||
@Inject
|
||||
lateinit var pump: Pump
|
||||
|
||||
@Inject
|
||||
lateinit var dispatcher: Dispatcher
|
||||
|
||||
// @MicronautTest 复用同一上下文:用例前清空内存 stub,避免全局状态跨用例串扰
|
||||
@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(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))
|
||||
assertTrue(ctx.getBeansOfType(Pump::class.java).isNotEmpty())
|
||||
assertTrue(ctx.getBeansOfType(Dispatcher::class.java).isNotEmpty())
|
||||
assertNotNull(controller)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `accept then pump tick transitions message to FAILED CODEC_ERROR with backoff`() {
|
||||
val receipt = controller.send("<MSG/>")
|
||||
assertNotNull(receipt.body()) // 受理 ID
|
||||
val id = receipt.body()!!.toLong()
|
||||
|
||||
pump.tick() // 主泵领取 → stub codec 未实装 → FAILED(CODEC_ERROR)
|
||||
|
||||
val stub = ctx.getBean(StubProcState::class.java)
|
||||
val s = stub.snapshotOf(id)
|
||||
assertNotNull(s)
|
||||
assertEquals(ProcStatus.FAILED, s!!.state)
|
||||
assertEquals(ErrorClass.CODEC_ERROR, s.errorClass)
|
||||
assertEquals(1, s.attempts)
|
||||
assertNotNull(s.nextAttemptAt)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `replay reopens failed CODEC_ERROR row to PENDING`() {
|
||||
val receipt = controller.send("<MSG/>")
|
||||
val id = receipt.body()!!.toLong()
|
||||
pump.tick()
|
||||
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)
|
||||
.replay(listOf(ErrorClass.CODEC_ERROR))
|
||||
|
||||
assertEquals(1, n)
|
||||
val reopened = stub.snapshotOf(id)!!
|
||||
assertEquals(ProcStatus.PENDING, reopened.state)
|
||||
assertEquals(0, reopened.attempts)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `dispatcher flushes schd batch through stub port`() {
|
||||
val events = ctx.getBean(StubMsgEvents::class.java)
|
||||
val port = ctx.getBean(StubDeliveryPort::class.java)
|
||||
events.insertAll(listOf(
|
||||
MsgEvent(target = Targets.KAFKA_SCHD, partitionKey = "F1", payloadJson = """{"FLID":"F1","v":"old"}"""),
|
||||
MsgEvent(target = Targets.KAFKA_SCHD, partitionKey = "F1", payloadJson = """{"FLID":"F1","v":"new"}"""),
|
||||
))
|
||||
|
||||
dispatcher.flushSchd()
|
||||
|
||||
assertEquals(1, port.sent.count { it.first == "schd" })
|
||||
val payload = port.sent.first { it.first == "schd" }.second
|
||||
assertTrue(payload.contains("new") && !payload.contains("old"))
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,14 @@
|
||||
# U03 配置绑定回归测试专用环境(@MicronautTest 默认 environment=test)
|
||||
# 目的:让 ApplicationContext 可无外部依赖启动(内存 datasource / 关闭迁移 / 关闭注册),
|
||||
# 以便以「注入 PipelineProps 断言」离线验证 msgx.* 嵌套键绑定(N02 验收),不依赖 /env 端点。
|
||||
# 以便以「注入 PipelineProps/DataSource 断言」离线验证绑定(N02/R09/R01a),不依赖 /env 端点。
|
||||
# U07:stubs=true 提供内存仓储使全链路(Controller→Inbox→Pump→Dispatcher)可装配;
|
||||
# autostart=false:测试内不自动拉起后台循环(避免泄漏线程),按需手动 tick。
|
||||
msgx:
|
||||
register-eureka: false
|
||||
stubs: true
|
||||
pipeline:
|
||||
autostart: false
|
||||
|
||||
datasources:
|
||||
default:
|
||||
url: jdbc:h2:mem:cfgtest;DB_CLOSE_DELAY=-1
|
||||
@@ -20,9 +28,6 @@ kafka:
|
||||
bootstrap:
|
||||
servers: 127.0.0.1:9092
|
||||
|
||||
msgx:
|
||||
register-eureka: false
|
||||
|
||||
micronaut:
|
||||
server:
|
||||
port: 0
|
||||
|
||||
Reference in New Issue
Block a user