feat: scaffold Micronaut+Kotlin service per ACMA-8 v4 architecture
- 4 modules (ingress/processing/delivery/reference) + codec + infra + jobs - Flyway V2.0.0: six aux tables (PROC_STATE/MSG_EVENT/REF_DATA/REQ_TRACK/PUMP_JOB/FLIGHT_STATE) - Lua: snapshot_replace (atomic cover+gen-diff-delete), batch_delete (3:30 sweep) - config: env-externalized secrets, phase A/B switch, ACMA-8 param-table defaults - logback: logstash TCP JSON + traceId MDC - pure-logic tests: identity (I3), schd aggregation max-by-EVENT_ID (FIX #7) - build verified: gradle compile+test green on JDK 25 (Micronaut 5.1 requires JVM 25+) Migrated from legacy repo subdirectory per repo-strategy decision (separate repo per service, org convention). Refs: ACMA-9, ACMA-8 v4, ACMA-6
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
package com.gzzn.omms.msgexchange.nextgen.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 org.junit.jupiter.api.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
/**
|
||||
* ACMA-8 流程 3 / 兼容矩阵 #7(FIX):同一 FLID 多次变更只发最新一态;
|
||||
* v4 显式“组内取 EVENT_ID 最大”,不依赖集合遍历序。
|
||||
*/
|
||||
class SchdAggregationTest {
|
||||
|
||||
private fun ev(flid: String, eventId: Long, payload: String) = MsgEvent(
|
||||
eventId = eventId,
|
||||
target = Targets.KAFKA_SCHD,
|
||||
partitionKey = flid,
|
||||
payloadJson = payload,
|
||||
state = EventStatus.PENDING,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `same flight collapsed to max EVENT_ID`() {
|
||||
val pending = listOf(
|
||||
ev("F1", 1, """{"FLID":"F1","v":"old"}"""),
|
||||
ev("F2", 2, """{"FLID":"F2","v":"only"}"""),
|
||||
ev("F1", 3, """{"FLID":"F1","v":"new"}"""),
|
||||
)
|
||||
val latest = SchdAggregation.latestPerFlight(pending)
|
||||
assertEquals(2, latest.size)
|
||||
assertEquals("""{"FLID":"F1","v":"new"}""", latest.first { it.contains("F1") })
|
||||
assertEquals("""{"FLID":"F2","v":"only"}""", latest.first { it.contains("F2") })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `order-independent grouping yields same latest per flight`() {
|
||||
val a = listOf(
|
||||
ev("F1", 1, "old"),
|
||||
ev("F1", 2, "new"),
|
||||
)
|
||||
val b = a.reversed()
|
||||
assertEquals(
|
||||
SchdAggregation.latestPerFlight(a).toSet(),
|
||||
SchdAggregation.latestPerFlight(b).toSet(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `wire shape is FLTR JSON array`() {
|
||||
val payload = SchdAggregation
|
||||
.latestPerFlight(listOf(ev("F1", 1, "a"), ev("F2", 2, "b")))
|
||||
.joinToString(",", "[", "]")
|
||||
assertEquals("[a,b]", payload)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.gzzn.omms.msgexchange.nextgen.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 org.junit.jupiter.api.Test
|
||||
import java.time.LocalDate
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
/**
|
||||
* ACMA-8 I3:identity = SNDR|TYPE|STYP|SEQN;日边界含否集中可配(CONFIRM 矩阵 #11,默认关)。
|
||||
*/
|
||||
class IdentityTest {
|
||||
|
||||
private fun msg(seqn: Long, dttm: Long = 20260906120000) = DecodedMessage(
|
||||
meta = MetaFields(sndr = "AODB", type = "FLOP", styp = "DELY", seqn = seqn, dttm = dttm),
|
||||
kind = MsgKind.Flop("DELY"),
|
||||
rawXml = "<MSG/>",
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `identity is pipe-joined SNDR TYPE STYP SEQN`() {
|
||||
val identity = Identity.of(msg(42), includeDayBoundary = false)
|
||||
assertEquals("AODB|FLOP|DELY|42", identity)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `day boundary included only when configured`() {
|
||||
val day = LocalDate.of(2026, 9, 6)
|
||||
assertEquals(
|
||||
"AODB|FLOP|DELY|42|2026-09-06",
|
||||
Identity.of(msg(42), includeDayBoundary = true, day = day),
|
||||
)
|
||||
assertEquals(
|
||||
"AODB|FLOP|DELY|42",
|
||||
Identity.of(msg(42), includeDayBoundary = false, day = day),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `props default keeps day boundary off`() {
|
||||
assertEquals(false, PipelineProps.Identity().includeDayBoundary)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user