fix(jobs): recheck archived flight version before purge
HistorySweepJob 归档成功后进 PIPELINE_LOCK 事务,按 (FLID, STATE_VERSION) 用同一判据复查,仍合格才登记 tombstone 并删除;purgeArchived 改收候选而非裸 FLID 列表,并带 (flid, state_version) 删除守卫。事件与删除同事务提交或回滚(INV-17、INV-18)。 验证:./gradlew test 131 tests / 0 fail(新增「归档后主泵更新同一 FLID → 不删、不发 tombstone」用例)。tombstone 与删除的回滚原子性依赖真实 PG 事务,本机无 Docker 时无法执行。
This commit is contained in:
@@ -7,6 +7,8 @@ import com.gzzn.omms.msgexchange.domain.flight.FlightState
|
||||
import com.gzzn.omms.msgexchange.domain.flight.HistoryCandidate
|
||||
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 org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.Test
|
||||
@@ -51,7 +53,7 @@ class HistorySweepJobTest {
|
||||
fun `history store not connected must delete zero rows`() {
|
||||
val f = seededFlight("F1", deleted = true, idleDays = 30)
|
||||
val events = StubMsgEvents()
|
||||
val job = HistorySweepJob(f, events, HistoryProps().apply { historyStoreEnabled = false }, OperationDayProps())
|
||||
val job = HistorySweepJob(f, events, HistoryProps().apply { historyStoreEnabled = false }, OperationDayProps(), StubPipelineTx(), StubPipelineLock())
|
||||
|
||||
val outcome = job.run(now)
|
||||
|
||||
@@ -72,7 +74,7 @@ class HistorySweepJobTest {
|
||||
val events = StubMsgEvents()
|
||||
val store = RecordingHistoryStore().apply { confirmed.add("F1") } // 只有 F1 归档确认成功
|
||||
val job = HistorySweepJob(
|
||||
f, events, HistoryProps().apply { historyStoreEnabled = true }, OperationDayProps(), historyStore = store,
|
||||
f, events, HistoryProps().apply { historyStoreEnabled = true }, OperationDayProps(), StubPipelineTx(), StubPipelineLock(), historyStore = store,
|
||||
)
|
||||
|
||||
val outcome = job.run(now)
|
||||
@@ -91,7 +93,7 @@ class HistorySweepJobTest {
|
||||
val events = StubMsgEvents()
|
||||
val store = RecordingHistoryStore()
|
||||
val job = HistorySweepJob(
|
||||
f, events, HistoryProps().apply { historyStoreEnabled = true }, OperationDayProps(), historyStore = store,
|
||||
f, events, HistoryProps().apply { historyStoreEnabled = true }, OperationDayProps(), StubPipelineTx(), StubPipelineLock(), historyStore = store,
|
||||
)
|
||||
|
||||
job.run(now)
|
||||
@@ -103,13 +105,37 @@ class HistorySweepJobTest {
|
||||
assertEquals(null, f.findMainRow("F3"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a flight updated after archival is not purged and emits no tombstone`() {
|
||||
val f = seededFlight("F4", deleted = false, idleDays = 30)
|
||||
val events = StubMsgEvents()
|
||||
val store = object : HistorySweepJob.HistoryStore {
|
||||
override fun archive(candidates: List<HistoryCandidate>): Set<String> {
|
||||
// 模拟归档期间主泵对同一 FLID 提交了新版本:清理判据已不再成立/版本已变
|
||||
f.markDeleted("F4", msgId = 9, now = now)
|
||||
return setOf("F4")
|
||||
}
|
||||
}
|
||||
val job = HistorySweepJob(
|
||||
f, events, HistoryProps().apply { historyStoreEnabled = true }, OperationDayProps(),
|
||||
StubPipelineTx(), StubPipelineLock(), historyStore = store,
|
||||
)
|
||||
|
||||
val outcome = job.run(now)
|
||||
|
||||
assertEquals(1, outcome.archived)
|
||||
assertEquals(0, outcome.purged) // 归档后版本变了:本次不删
|
||||
assertTrue(f.findMainRow("F4") != null) // 在用航班不许被清掉
|
||||
assertEquals(0, events.rows.size) // 也不登记 tombstone
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `snap log purge runs independently of the history store switch`() {
|
||||
val f = seededFlight("F1", deleted = true, idleDays = 30)
|
||||
var cutoff: Instant? = null
|
||||
val purge = com.gzzn.omms.msgexchange.infra.persistence.SnapshotLogPurge { instant -> cutoff = instant; 7 }
|
||||
val job = HistorySweepJob(
|
||||
f, StubMsgEvents(), HistoryProps().apply { historyStoreEnabled = false }, OperationDayProps(), snapLogPurge = purge,
|
||||
f, StubMsgEvents(), HistoryProps().apply { historyStoreEnabled = false }, OperationDayProps(), StubPipelineTx(), StubPipelineLock(), snapLogPurge = purge,
|
||||
)
|
||||
|
||||
val outcome = job.run(now)
|
||||
|
||||
Reference in New Issue
Block a user