feat(flight): UNMAPPED_FIELD、SRVT/VIPF 明细与白名单内未映射留档(ACM2-89/99/100)
V5 迁移;集合段出现才写;白名单 7 类内未落态写入 UNMAPPED_FIELD,不扩白名单。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,148 @@
|
||||
package com.gzzn.omms.msgexchange.processing
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.gzzn.omms.msgexchange.codec.FlopPayload
|
||||
import com.gzzn.omms.msgexchange.codec.JacksonXmlCodec
|
||||
import com.gzzn.omms.msgexchange.domain.DecodedMessage
|
||||
import com.gzzn.omms.msgexchange.domain.MetaFields
|
||||
import com.gzzn.omms.msgexchange.domain.MsgKind
|
||||
import com.gzzn.omms.msgexchange.domain.ProcState
|
||||
import com.gzzn.omms.msgexchange.domain.ProcStatus
|
||||
import com.gzzn.omms.msgexchange.domain.flight.FlightState
|
||||
import com.gzzn.omms.msgexchange.domain.flight.UnmappedFieldEntry
|
||||
import com.gzzn.omms.msgexchange.infra.stub.StubFlightProjectionPort
|
||||
import com.gzzn.omms.msgexchange.infra.stub.StubFlightState
|
||||
import com.gzzn.omms.msgexchange.infra.stub.StubMsgEvents
|
||||
import com.gzzn.omms.msgexchange.infra.stub.StubPipelineLock
|
||||
import com.gzzn.omms.msgexchange.infra.stub.StubPipelineTx
|
||||
import com.gzzn.omms.msgexchange.infra.stub.StubProcState
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.time.LocalDate
|
||||
|
||||
class SrvtVipfUnmappedTest {
|
||||
|
||||
private val msgId = 42L
|
||||
private val flid = "900001"
|
||||
|
||||
private fun head() = ProcState(msgId, ProcStatus.PENDING, updatedAt = java.time.Instant.EPOCH)
|
||||
|
||||
private fun flopProcessor(flights: StubFlightState): FlopProcessor {
|
||||
val commit = FlightCommit(
|
||||
StubPipelineTx(),
|
||||
StubPipelineLock(),
|
||||
StubProcState(),
|
||||
StubMsgEvents(),
|
||||
StubFlightProjectionPort(),
|
||||
java.time.Clock.systemUTC(),
|
||||
)
|
||||
return FlopProcessor(commit, flights, StubMsgEvents(), ObjectMapper(), java.time.Clock.systemUTC())
|
||||
}
|
||||
|
||||
private fun seedFlight(flights: StubFlightState) {
|
||||
flights.persistFullState(
|
||||
com.gzzn.omms.msgexchange.domain.flight.FlightSnapshot(
|
||||
flid,
|
||||
LocalDate.of(2026, 12, 15),
|
||||
FlightState.ACTIVE,
|
||||
1,
|
||||
mapOf("FLNO" to "CA101"),
|
||||
emptyMap(),
|
||||
),
|
||||
msgId = 1,
|
||||
now = java.time.Instant.EPOCH,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `SRVT present writes detail absent keeps prior rows`() {
|
||||
val flights = StubFlightState()
|
||||
seedFlight(flights)
|
||||
val proc = flopProcessor(flights)
|
||||
val srvt = listOf(mapOf("OPER" to "ADD", "SRTC" to "SR01"))
|
||||
proc.apply(
|
||||
head(),
|
||||
flopMsg("GTDT"),
|
||||
FlopPayload(flid, collections = mapOf("SRVT" to srvt)),
|
||||
)
|
||||
assertEquals(srvt, flights.srvtByFlid[flid])
|
||||
|
||||
proc.apply(
|
||||
head(),
|
||||
flopMsg("GTDT"),
|
||||
FlopPayload(flid, collections = mapOf("GTDT" to listOf(mapOf("GTNO" to "1", "GATE" to "D01")))),
|
||||
)
|
||||
assertEquals(srvt, flights.srvtByFlid[flid], "absent SRVT must not clear Q2 default")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `VIPF present and reprocess replaces segment idempotently`() {
|
||||
val flights = StubFlightState()
|
||||
seedFlight(flights)
|
||||
val proc = flopProcessor(flights)
|
||||
val vipf = listOf(mapOf("VPCD" to "V001", "VFES" to "3"))
|
||||
val payload = FlopPayload(flid, collections = mapOf("VIPF" to vipf))
|
||||
proc.apply(head(), flopMsg("GTDT"), payload)
|
||||
proc.apply(head(), flopMsg("GTDT"), payload)
|
||||
assertEquals(1, flights.vipfByFlid[flid]!!.size)
|
||||
assertEquals("V001", flights.vipfByFlid[flid]!![0]["VPCD"])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `whitelisted FLOP with FDIV stores unmapped fields`() {
|
||||
val codec = JacksonXmlCodec()
|
||||
val raw = """
|
||||
<MSG><META><SNDR>AODB</SNDR><SEQN>1</SEQN><DTTM>20021010090311</DTTM>
|
||||
<TYPE>FLOP</TYPE><STYP>GTDT</STYP></META>
|
||||
<FLOP><FLID>$flid</FLID>
|
||||
<GTDT GTNO="1"><GATE>D08</GATE></GTDT>
|
||||
<FDIV DDES="CTU" DDIR="TO">note</FDIV>
|
||||
</FLOP></MSG>
|
||||
""".trimIndent()
|
||||
val decoded = (codec.decode(raw) as com.gzzn.omms.msgexchange.codec.DecodeResult.Ok).message
|
||||
val body = decoded.body as FlopPayload
|
||||
assertTrue(body.unmapped.any { it.path == "FDIV/@DDES" && it.raw == "CTU" })
|
||||
assertTrue(body.unmapped.any { it.path == "FDIV/@DDIR" && it.raw == "TO" })
|
||||
assertTrue(body.unmapped.any { it.path == "FDIV" && it.raw == "note" })
|
||||
|
||||
val flights = StubFlightState()
|
||||
seedFlight(flights)
|
||||
flopProcessor(flights).apply(head(), decoded, body)
|
||||
assertEquals(body.unmapped, flights.unmappedByMsgId[msgId])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `reprocess rewrites same unmapped rows`() {
|
||||
val flights = StubFlightState()
|
||||
seedFlight(flights)
|
||||
val proc = flopProcessor(flights)
|
||||
val unmapped = listOf(
|
||||
UnmappedFieldEntry("FRET/@REID", 1, "G"),
|
||||
UnmappedFieldEntry("FRET", 1, ""),
|
||||
)
|
||||
val payload = FlopPayload(flid, unmapped = unmapped)
|
||||
proc.apply(head(), flopMsg("DELY"), payload)
|
||||
proc.apply(head(), flopMsg("DELY"), payload)
|
||||
assertEquals(unmapped, flights.unmappedByMsgId[msgId])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `FLOP FDIV subtype stays unsupported not unmapped`() {
|
||||
val codec = JacksonXmlCodec()
|
||||
val raw = """
|
||||
<MSG><META><SNDR>AODB</SNDR><SEQN>2</SEQN><DTTM>20021010090311</DTTM>
|
||||
<TYPE>FLOP</TYPE><STYP>FDIV</STYP></META>
|
||||
<FLOP><FLID>$flid</FLID><FDIV DDES="CTU"/></FLOP></MSG>
|
||||
""".trimIndent()
|
||||
val kind = (codec.decode(raw) as com.gzzn.omms.msgexchange.codec.DecodeResult.Ok).message.kind
|
||||
assertEquals(MsgKind.Unsupported("FLOP-FDIV"), kind)
|
||||
}
|
||||
|
||||
private fun flopMsg(styp: String) = DecodedMessage(
|
||||
meta = MetaFields("AODB", "FLOP", styp, 1L, 1L),
|
||||
kind = MsgKind.Flop(styp),
|
||||
rawXml = "<MSG/>",
|
||||
body = FlopPayload(flid),
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user