fix(processing): 合法不支持类型改记 SKIPPED 终态,参考数据单列 RefData(ACM2-83)

- MsgKind.RefData 承接 SIS:3.1~3.14 类别码,白名单分派对齐 implementation.md
- Unsupported 由 FAILED(UNSUPPORTED) 重试改为 SKIPPED(unsupported) 终态(US-03 AC2)
- RefData 在 ReferenceDataProcessor 实装前保持可重试失败,不被吞为跳过
- 同步更新 IgnoreBranchTest / PipelineSmokeTest 旧断言,补 codec 分派回归
This commit is contained in:
windyboy
2026-09-21 11:41:34 +08:00
parent 32bbd459c1
commit 3c577b4d1e
6 changed files with 91 additions and 18 deletions
@@ -78,36 +78,42 @@ class PipelineSmokeTest {
}
companion object {
/** 报文头合法,但类型没有对应处理器,会走"暂不支持、先重试"这条路 */
/** 报文头合法,但类型没有对应处理器且不属参考数据 → 跳过留档(US-03 AC2) */
val UNSUPPORTED_XML = """
<MSG>
<META><SNDR>AODB</SNDR><SEQN>1</SEQN><DTTM>20260908120000</DTTM><TYPE>XYZQ</TYPE><STYP>FOO</STYP></META>
</MSG>
""".trimIndent()
/** 参考数据类别(SIS:3.2):ReferenceDataProcessor 未实装(ACM2-93),先按可重试失败留队 */
val REFDATA_XML = """
<MSG>
<META><SNDR>AODB</SNDR><SEQN>2</SEQN><DTTM>20260908120000</DTTM><TYPE>ARPT</TYPE><STYP>DNLD</STYP></META>
</MSG>
""".trimIndent()
}
@Test
fun `accept then pump tick transitions message to FAILED UNSUPPORTED with backoff`() {
fun `accept then pump tick skips unknown type as SKIPPED with backfill intent`() {
val receipt = controller.send(UNSUPPORTED_XML)
assertNotNull(receipt.body()) // 受理 ID
val id = receipt.body()!!.toLong()
// 兼容入口只保证"已落信 + 已入队",**不推进水位**;必须先被收报发现(W 追平)才可领取。
ctx.getBean(com.gzzn.omms.msgexchange.ingress.InboxPoller::class.java).pollOnce(Instant.now())
pump.tick() // 解码成功但无 DELY Handler → FAILED(UNSUPPORTED)
pump.tick() // 解码成功但无 XYZQ Handler → SKIPPED(unsupported)
val stub = ctx.getBean(StubProcState::class.java)
val s = stub.snapshotOf(id)
assertNotNull(s)
assertEquals(ProcStatus.FAILED, s!!.state)
assertEquals(ErrorClass.UNSUPPORTED, s.errorClass)
assertEquals(1, s.attempts)
assertNotNull(s.nextAttemptAt)
assertEquals(ProcStatus.SKIPPED, s!!.state)
assertEquals("unsupported:XYZQ-FOO", s.lastError)
assertNotNull(s.backfillNextAt)
}
@Test
fun `replay reopens failed UNSUPPORTED row to PENDING`() {
val receipt = controller.send(UNSUPPORTED_XML)
val receipt = controller.send(REFDATA_XML)
val id = receipt.body()!!.toLong()
ctx.getBean(com.gzzn.omms.msgexchange.ingress.InboxPoller::class.java).pollOnce(Instant.now())
pump.tick()
@@ -251,8 +257,8 @@ class PipelineSmokeTest {
poller.pollOnce(Instant.now())
assertTrue(cursor.cursor.committedUpTo >= high)
repeat(3) { pump.tick() } // 依次处理 2、3、高 ID
assertEquals(ProcStatus.FAILED, proc.find(high)!!.state)
assertEquals(ErrorClass.UNSUPPORTED, proc.find(high)!!.errorClass)
assertEquals(ProcStatus.SKIPPED, proc.find(high)!!.state)
assertEquals("unsupported:XYZQ-FOO", proc.find(high)!!.lastError)
}
@Test
@@ -232,4 +232,22 @@ class JacksonXmlCodecTest {
assertEquals(MsgKind.Fdel, result.message.kind)
assertEquals("121112312", (result.message.body as FlopPayload).flid)
}
@Test
fun `reference data category types dispatch to RefData and unknown types stay Unsupported`() {
fun raw(type: String, styp: String) = """
<MSG><META><SNDR>AODB</SNDR><SEQN>9</SEQN><DTTM>20021010090311</DTTM>
<TYPE>$type</TYPE><STYP>$styp</STYP></META></MSG>
""".trimIndent()
// SIS:3.13.14 类别码单列为 RefDataUS-13),任一 STYP 都不得落成 Unsupported
for (type in listOf("COUL", "ARPT", "AIRL", "AIRC", "REGN", "ORGN", "FLTL", "TLST",
"GLST", "SLST", "CLST", "BLST", "CHLT", "RSTA")) {
val msg = (codec.decode(raw(type, "DNLD")) as DecodeResult.Ok).message
assertEquals(MsgKind.RefData(type), msg.kind)
}
val unknown = (codec.decode(raw("XYZZ", "TEST")) as DecodeResult.Ok).message
assertEquals(MsgKind.Unsupported("XYZZ-TEST"), unknown.kind)
}
}
@@ -30,7 +30,8 @@ import java.time.Instant
/**
* 忽略类报文(US-04):命中忽略清单的报文先绑定身份再写 SKIPPED,
* 不产生航班或 outbox 副作用;未命中的合法类型继续按 UNSUPPORTED 处理。
* 不产生航班或 outbox 副作用;未命中且无处理器的合法类型同样落 SKIPPED(unsupported)
* 终态(US-03 AC2),参考数据类别(US-13)在处理器落地前保持可重试失败。
*/
class IgnoreBranchTest {
@@ -173,7 +174,7 @@ class IgnoreBranchTest {
}
@Test
fun `non-ignored unsupported type continues as UNSUPPORTED`() {
fun `non-ignored unsupported type is skipped as terminal with backfill intent`() {
val proc = StubProcState()
proc.insertIfAbsent(msgId, null)
val inbox = StubInbox()
@@ -184,10 +185,33 @@ class IgnoreBranchTest {
p.processOne(head())
assertEquals(ProcStatus.FAILED, proc.find(msgId)!!.state)
val row = proc.find(msgId)!!
assertEquals(ProcStatus.SKIPPED, row.state)
assertEquals("unsupported:XYZZ-TEST", row.lastError)
assertNotNull(row.backfillNextAt)
assertEquals(0L, counters.ignoredCount())
}
@Test
fun `reference data type without handler stays retryable instead of being swallowed`() {
val proc = StubProcState()
proc.insertIfAbsent(msgId, null)
val inbox = StubInbox()
inbox.raws[msgId] = "<RAW/>"
val msg = DecodedMessage(
meta = MetaFields("AODB", "ARPT", "DNLD", 400L, 1L),
kind = MsgKind.RefData("ARPT"),
rawXml = "<RAW/>",
)
val p = processor(proc = proc, inbox = inbox, codec = codecReturning(msg))
p.processOne(head())
val row = proc.find(msgId)!!
assertEquals(ProcStatus.FAILED, row.state)
assertEquals("refdata-pending:ARPT", row.lastError)
}
@Test
fun `ignore increments counter`() {
val proc = StubProcState()