docs(kdoc): 其余模块注释统一为直白说明
把上一提交没覆盖到的模块也改完,至此仓库内不再有"只写编号、不写说明"的注释。 涉及 33 个文件(生产 24 + 测试 9),包括航班状态模型与合并引擎、事件与留痕模型、 运营日计算、投递器、历史归档作业、编解码与身份、重试与重放、健康指示器、 生命周期装配、以及各自的测试类。 改法与前面一致:先说这段代码做什么,再说为什么这么做,术语第一次出现就地解释 (FLID、OPERATION_DAY、航班版本号、删除通知、待发事件表等)。工单编号 (U07/N02/ACM2-10 之类)已全部移除;段落编号只剩 10 处,全部带文件名位于句尾, 用作延伸阅读,例如"见 docs/flight-state.md §5"。 保留了本来就自解释的行内注释、PgTestSupport 顶部的环境变量默认值表格, 以及 InboxController 里描述待补接口的 TODO。 校验:逐文件做"去注释后比对"(块注释 + 行注释清除、空白归一),33 个文件代码 零差异;另做逐行非注释代码比对,同样零差异。clean test 为 78 passed / 1 skipped。
This commit is contained in:
@@ -8,9 +8,11 @@ import java.sql.Connection
|
||||
import javax.sql.DataSource
|
||||
|
||||
/**
|
||||
* U03(N02/R09/R01a 验收的“启动级”版本):基础设施配置在真实上下文启动时绑定生效——
|
||||
* 注入 DataSource 并建立真实连接(H2 内存,application-test.yml),证明 datasources.default.*
|
||||
* 键位与驱动解析正确(而非“看似配置实则未生效”)。Pump 等业务 bean 懒加载,不依赖实仓储。
|
||||
* 守着启动期配置真的生效:在完整应用上下文里注入 DataSource 并真连一次(H2 内存库,配在
|
||||
* application-test.yml),证明 datasources.default.* 的键位和驱动都能解析,而不是
|
||||
* "看着配了、其实没生效"。
|
||||
*
|
||||
* 业务 bean(比如主泵)是懒加载的,所以这里不需要真实仓储。
|
||||
*/
|
||||
@MicronautTest
|
||||
class InfraBindingStartupTest {
|
||||
@@ -33,7 +35,7 @@ class InfraBindingStartupTest {
|
||||
@Test
|
||||
fun `registration disabled by test profile`() {
|
||||
assertNotNull(props)
|
||||
// eureka 注册在 application-test.yml 经 msgx.register-eureka=false 关闭——启动级验证绑定可达
|
||||
// 测试配置里关掉了 eureka 注册(msgx.register-eureka=false),这里验证上下文照样能起来
|
||||
assertNotNull(dataSource)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,10 +11,11 @@ import org.junit.jupiter.api.TestInstance
|
||||
import java.time.Duration
|
||||
|
||||
/**
|
||||
* U03(N02)验收:msgx.* 嵌套键「改值—回读」离线化。
|
||||
* 三个嵌套类(Pipeline/Schd/Identity)须标注 @ConfigurationProperties 才会被绑定;
|
||||
* 未标注时下列覆盖值会静默回落 Kotlin 默认值(本测试即红)。仅注入配置 bean,不触碰仓储/DB。
|
||||
* 注:Micronaut Test 5.x 移除 @Property 注解,改以 TestPropertyProvider 注入测试属性(需 PER_CLASS)。
|
||||
* 守着配置绑定真的生效:msgx.* 下面每个嵌套配置类都要标 @ConfigurationProperties,否则键不会
|
||||
* 被绑定,测试里给的覆盖值会静默失效、用回代码里的默认值,这个测试就会红。
|
||||
*
|
||||
* 只注入配置 bean,不碰仓储和数据库。另外注意 Micronaut Test 5.x 去掉了 @Property 注解,
|
||||
* 改由 TestPropertyProvider 提供测试属性,所以类上要加 PER_CLASS。
|
||||
*/
|
||||
@MicronautTest
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
@@ -41,6 +42,6 @@ class PipelinePropsBindingTest : TestPropertyProvider {
|
||||
@Test
|
||||
fun `top-level scalars still bind`() {
|
||||
assertEquals("msgexchangeapi", props.serviceName)
|
||||
assertFalse(props.registerEureka) // application-test.yml 置 false
|
||||
assertFalse(props.registerEureka) // application-test.yml 里配成 false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,8 @@ import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
/**
|
||||
* U08/N28:backoffFor 必须对 attempt ≤ 0(FAILED 行未递增 attempts)给出首档退避而非抛异常。
|
||||
* 守着退避表的规矩:按档位递增、超出表长就封顶;传入 0 或负数(比如还没记过重试次数的
|
||||
* FAILED 行)要返回第一档,不能抛异常。
|
||||
*/
|
||||
class PipelinePropsTest {
|
||||
|
||||
|
||||
@@ -16,9 +16,11 @@ import org.junit.jupiter.api.Test
|
||||
import java.time.Instant
|
||||
|
||||
/**
|
||||
* 投递调度(docs/flight-state.md §5 + design.md §5.1/§5.2):
|
||||
* KAFKA_MSG 逐条 FIFO;KAFKA_SCHD 唯一出口 flushSchd——同 FLID 未发事件按最新
|
||||
* STATE_VERSION 合并;TOMBSTONE 发 null 值消息;失败退避重试、达上限 DEAD。
|
||||
* 守着投递的几条规矩:
|
||||
* - KAFKA_MSG 一条条按顺序发,而且不会顺手把 KAFKA_SCHD 的事件发掉;
|
||||
* - KAFKA_SCHD 只有 flushSchd 一个出口,同一个 FLID(航班实例 ID)只发版本号最新的那条;
|
||||
* - 删除通知发成 value 为空的 tombstone 消息;
|
||||
* - 发送失败按退避重试,次数用尽转 DEAD 当死信。
|
||||
*/
|
||||
class DispatcherTickTest {
|
||||
|
||||
@@ -45,7 +47,7 @@ class DispatcherTickTest {
|
||||
d.tick()
|
||||
|
||||
assertEquals(1, port.sent.count { it.topic == "msg" })
|
||||
assertEquals(0, port.sent.count { it.topic == "schd" }) // N03:schd 唯一出口 flushSchd
|
||||
assertEquals(0, port.sent.count { it.topic == "schd" }) // KAFKA_SCHD 只能由 flushSchd 发,tick 不许碰
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -84,7 +86,7 @@ class DispatcherTickTest {
|
||||
d.flushSchd()
|
||||
|
||||
assertEquals(1, port.tombstones.size)
|
||||
assertEquals("F1", port.tombstones.single().key) // §5:整态键缺失表示删除旧值
|
||||
assertEquals("F1", port.tombstones.single().key) // value 为空表示删掉这个 key 的旧值
|
||||
assertNull(port.tombstones.single().payload)
|
||||
}
|
||||
|
||||
@@ -117,7 +119,7 @@ class DispatcherTickTest {
|
||||
}
|
||||
}
|
||||
|
||||
/** schd 发送恒失败的端口(退避/终态闭环验证用)。 */
|
||||
/** KAFKA_SCHD 发送永远失败的投递端口,用来验证退避重试和最终转死信的闭环。 */
|
||||
private class FailingSchdPort : DeliveryPort {
|
||||
var calls = 0
|
||||
override fun sendKafka(topic: String, key: String, payloadJson: String) = Unit
|
||||
|
||||
@@ -8,7 +8,8 @@ import java.time.LocalDate
|
||||
import java.time.ZoneId
|
||||
|
||||
/**
|
||||
* 运营日计算(docs/flight-state.md §2.1):SODT(ddMMMyyHHmm)+ 机场时区 + 业务切日边界。
|
||||
* 守着运营日的算法:用计划运行时间 SODT(ddMMMyyHHmm)加机场时区算,本地时刻早于切日边界的
|
||||
* 归到前一个运营日;越界的切日边界收敛到 23;SODT 缺失或格式不对就返回 null。
|
||||
*/
|
||||
class OperationDayTest {
|
||||
|
||||
|
||||
@@ -10,10 +10,11 @@ import java.time.LocalDate
|
||||
import java.time.ZoneId
|
||||
|
||||
/**
|
||||
* 航班状态引擎(docs/flight-state.md §2–§4)不变量:
|
||||
* 日计划合并(§3.1)——出现 Set/Replace、缺失保留、标量空值显式清空;
|
||||
* DELETED 不被日计划恢复(§3.3);FLOP/ADFT 增量合并(§3.2/§3.3);
|
||||
* 整包校验(§4 步骤 2:声明数量、航班标识、运营日推导)失败整包拒绝。
|
||||
* 守着航班状态合并的几条规矩:
|
||||
* - 日计划合并:报文里出现的字段覆盖本地值、没出现的保留,标量给空串等于显式清空;
|
||||
* - 日计划不能把已删除的航班恢复成在用;
|
||||
* - FLOP/ADFT 只改报文表达的字段和集合;
|
||||
* - 整包校验(条数、FLID 格式、运营日能不能算出来)有一条不过就整包拒收。
|
||||
*/
|
||||
class FlightStateEngineTest {
|
||||
|
||||
@@ -34,11 +35,11 @@ class FlightStateEngineTest {
|
||||
keepDeleted = false,
|
||||
)
|
||||
|
||||
assertEquals("CA002", next.scalars["FLNO"]) // 重叠字段被覆盖(§3.1)
|
||||
assertEquals("old-note", next.scalars["REMC"]) // 缺失标量保留(§3.1/§7)
|
||||
assertEquals("", next.scalars["CNCL"]) // 空串 = 显式清空(落库置 NULL)
|
||||
assertEquals(listOf(mapOf("GATE" to "G1")), next.collections["GTDT"]) // 缺失集合保留
|
||||
assertEquals(6, next.stateVersion) // 每次成功写入 +1
|
||||
assertEquals("CA002", next.scalars["FLNO"]) // 报文里出现的字段覆盖本地值
|
||||
assertEquals("old-note", next.scalars["REMC"]) // 报文没提的标量保留原值
|
||||
assertEquals("", next.scalars["CNCL"]) // 空串表示显式清空,落库时置成 NULL
|
||||
assertEquals(listOf(mapOf("GATE" to "G1")), next.collections["GTDT"]) // 报文没提的集合保留原值
|
||||
assertEquals(6, next.stateVersion) // 每成功合并一次,版本号加一
|
||||
assertEquals(FlightState.ACTIVE, next.state)
|
||||
}
|
||||
|
||||
@@ -76,8 +77,8 @@ class FlightStateEngineTest {
|
||||
assertEquals(FlightState.ACTIVE, next.state)
|
||||
assertEquals(1L, next.stateVersion)
|
||||
assertEquals("CA001", next.scalars["FLNO"])
|
||||
assertFalse(next.scalars.containsKey("REMC")) // 无历史可保留
|
||||
assertEquals(10, next.collections.size) // 全键输出(§2.2)
|
||||
assertFalse(next.scalars.containsKey("REMC")) // 新航班没有历史值可保留
|
||||
assertEquals(10, next.collections.size) // 新航班按全部集合键输出,没内容的给空集
|
||||
assertEquals(listOf(mapOf("GATE" to "G1")), next.collections["GTDT"])
|
||||
assertEquals(emptyList<Map<String, String>>(), next.collections["DELY"])
|
||||
}
|
||||
@@ -88,7 +89,7 @@ class FlightStateEngineTest {
|
||||
val next = FlightStateEngine.snapshotState(
|
||||
current, ScheduleRecord("121", mapOf("FLNO" to "CA001")), day, keepDeleted = true,
|
||||
)
|
||||
assertEquals(FlightState.DELETED, next.state) // §3.3:日计划不恢复 DELETED
|
||||
assertEquals(FlightState.DELETED, next.state) // 日计划不能把已删除的航班恢复成在用
|
||||
assertEquals(4, next.stateVersion)
|
||||
}
|
||||
|
||||
@@ -105,7 +106,7 @@ class FlightStateEngineTest {
|
||||
)
|
||||
|
||||
assertEquals("15DEC261900", next.scalars["ESTT"])
|
||||
assertEquals("CA001", next.scalars["FLNO"]) // 缺失 = 保留(§3.2)
|
||||
assertEquals("CA001", next.scalars["FLNO"]) // 增量里没提的字段保留原值
|
||||
assertEquals(listOf(mapOf("GATE" to "G1")), next.collections["GTDT"])
|
||||
assertEquals(2, next.stateVersion)
|
||||
}
|
||||
@@ -153,6 +154,6 @@ class FlightStateEngineTest {
|
||||
val tooLong = FlightStateEngine.validateMessage(
|
||||
1, listOf(ScheduleRecord("1234567890123", mapOf("SODT" to "15DEC261723"))), opDay,
|
||||
)
|
||||
assertTrue(tooLong is SnapshotValidation.Invalid) // FLID 数字型 Number(1-12)
|
||||
assertTrue(tooLong is SnapshotValidation.Invalid) // FLID 只能是 1–12 位纯数字
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,9 +12,10 @@ import org.junit.jupiter.api.Test
|
||||
import java.time.Instant
|
||||
|
||||
/**
|
||||
* 历史归档与物理清除(docs/flight-state.md §6,顺序不可颠倒;design.md §6.2):
|
||||
* 历史存储未接通必须删 0 条;先归档确认再物理清除;
|
||||
* 未经 FDEL 的航班在清除前补发删除事件。
|
||||
* 守着归档清理的三条规矩:
|
||||
* - 历史存储没接通就一条都不删;
|
||||
* - 先拿到归档确认再物理删除,没确认的留着下次重试;
|
||||
* - 从没收到过 FDEL(航班终止报文)就被清掉的航班,删除前要补发一次删除通知。
|
||||
*/
|
||||
class HistorySweepJobTest {
|
||||
|
||||
@@ -53,7 +54,7 @@ class HistorySweepJobTest {
|
||||
|
||||
val outcome = job.run(now)
|
||||
|
||||
assertEquals(0, outcome.purged) // §6 红线:历史存储未接通必须删 0 条
|
||||
assertEquals(0, outcome.purged) // 历史存储没接通,一条都不许删
|
||||
assertTrue(f.findMainRow("F1") != null)
|
||||
assertEquals(0, events.rows.size)
|
||||
}
|
||||
@@ -78,14 +79,14 @@ class HistorySweepJobTest {
|
||||
assertEquals(2, outcome.selected)
|
||||
assertEquals(1, outcome.archived)
|
||||
assertEquals(1, outcome.purged)
|
||||
assertEquals(null, f.findMainRow("F1")) // §6:归档确认成功 → 物理删除
|
||||
assertTrue(f.findMainRow("F2") != null) // 失败或不明确的保留重试(步骤 5)
|
||||
assertEquals(0, events.rows.size) // DELETED 航班清除不再发业务删除事件
|
||||
assertEquals(null, f.findMainRow("F1")) // 归档确认成功的才物理删除
|
||||
assertTrue(f.findMainRow("F2") != null) // 归档没确认的留着下次再试
|
||||
assertEquals(0, events.rows.size) // 早就标记删除的航班,清理时不用再发删除通知
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `never-fdel lifecycle purge emits tombstone before deletion`() {
|
||||
val f = seededFlight("F3", deleted = false, idleDays = 30) // ACTIVE 且超兜底窗(§6 静默判据)
|
||||
val f = seededFlight("F3", deleted = false, idleDays = 30) // 还在用,但已经静默超过兜底期限,命中清理条件
|
||||
val events = StubMsgEvents()
|
||||
val store = RecordingHistoryStore()
|
||||
val job = HistorySweepJob(
|
||||
@@ -94,7 +95,7 @@ class HistorySweepJobTest {
|
||||
|
||||
job.run(now)
|
||||
|
||||
// §3.3/§5/§6:未经 FDEL、由生命周期直接清除的航班,清除前补发一次删除事件
|
||||
// 从没收到过 FDEL、被生命周期直接清掉的航班,删除前要补发一次删除通知
|
||||
val tombstone = events.rows.values.single { it.eventType == EventType.TOMBSTONE }
|
||||
assertEquals("F3", tombstone.partitionKey)
|
||||
assertTrue(tombstone.payloadJson.contains("\"deleted\":true"))
|
||||
|
||||
@@ -9,7 +9,8 @@ import java.time.LocalDate
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
/**
|
||||
* ACMA-8 I3:identity = SNDR|TYPE|STYP|SEQN;日边界含否集中可配(CONFIRM 矩阵 #11,默认关)。
|
||||
* 守着幂等键的规矩:发送方、类型、子类型、流水号四段用竖线拼起来;只有配置打开时才追加
|
||||
* 日期段用来按天去重,默认不追加。
|
||||
*/
|
||||
class IdentityTest {
|
||||
|
||||
|
||||
@@ -3,13 +3,13 @@ package com.gzzn.omms.msgexchange.support
|
||||
import org.testcontainers.containers.PostgreSQLContainer
|
||||
|
||||
/**
|
||||
* JDBC 集成测试 PostgreSQL 连接解析(ACM2-29 P3-A)。
|
||||
*
|
||||
* 优先级:
|
||||
* 1. 显式环境变量 `MSGX_PG_URL` / `MSGX_PG_HOST` / `MSGX_PG_PORT` / `MSGX_PG_NAME`(外接库/CI 固定库);
|
||||
* 2. Testcontainers 自动起 `postgres:17-alpine` 隔离容器(未配置环境变量且 docker 可用时;
|
||||
* JVM 单例,首个用例启动后全程复用,容器随 JVM 退出由 Ryuk 清理);
|
||||
* 3. 都不可用 → canConnect()=false,用例 assumeTrue 跳过(绝不误报通过)。
|
||||
* 给 JDBC 集成测试找一个能用的 PostgreSQL,按下面的顺序挑:
|
||||
* 1. 环境变量显式指定(MSGX_PG_URL / MSGX_PG_HOST / MSGX_PG_PORT / MSGX_PG_NAME),
|
||||
* 适合外接库或 CI 上的固定库;
|
||||
* 2. 没配环境变量且 docker 可用时,用 Testcontainers 起一个 postgres:17-alpine 容器:
|
||||
* JVM 内只起一个、全程复用,进程退出后由 Ryuk 清掉;
|
||||
* 3. 两条路都走不通时 canConnect() 返回 false,用例里用 assumeTrue 跳过,
|
||||
* 绝不因为连不上就当成通过。
|
||||
*
|
||||
* | 变量 | 默认 |
|
||||
* |---|---|
|
||||
|
||||
Reference in New Issue
Block a user