fix(infra): enforce one open request
新增 V8 部分唯一索引 uq_req_open:(REQ_TYPE, OPERATION_DAY, SENDER) 仅在 PENDING/SENT 唯一;建索引前用 DO 块检测存量重复组并以带业务键的异常中止迁移,禁止静默删数据。保留 idx_req_open 服务含关闭态的查询;不引入运行时协调器(归 US-08)。 验证:全量 ./gradlew test 126 tests / 0 fail;索引与拒绝第二条开放请求的断言在 FlywayMigrationTest(需真实 PG,本机无 Docker 时按既有 assumeTrue 跳过)。
This commit is contained in:
+35
-1
@@ -3,6 +3,7 @@ package com.gzzn.omms.msgexchange.infra.persistence.jdbc
|
||||
import com.gzzn.omms.msgexchange.support.PgTestSupport
|
||||
import org.flywaydb.core.Flyway
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertThrows
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.Assumptions.assumeTrue
|
||||
import org.junit.jupiter.api.Test
|
||||
@@ -48,7 +49,7 @@ class FlywayMigrationTest {
|
||||
while (rs.next()) {
|
||||
records.add(Triple(rs.getString("version"), rs.getString("script"), rs.getBoolean("success")))
|
||||
}
|
||||
assertTrue(records.size >= 7, "flyway_schema_history must record all migrations")
|
||||
assertTrue(records.size >= 8, "flyway_schema_history must record all migrations")
|
||||
assertEquals("1", records[0].first)
|
||||
assertEquals("V1__flight_state_baseline.sql", records[0].second)
|
||||
assertEquals("2", records[1].first)
|
||||
@@ -63,6 +64,8 @@ class FlywayMigrationTest {
|
||||
assertEquals("V6__enqueued_at.sql", records[5].second)
|
||||
assertEquals("7", records[6].first)
|
||||
assertEquals("V7__drop_processing_started_at.sql", records[6].second)
|
||||
assertEquals("8", records[7].first)
|
||||
assertEquals("V8__req_track_open_unique.sql", records[7].second)
|
||||
assertTrue(records.all { it.third })
|
||||
}
|
||||
|
||||
@@ -114,6 +117,37 @@ class FlywayMigrationTest {
|
||||
assertEquals(0, rs.getInt(1), "V7 必须已删除 PROCESSING_STARTED_AT")
|
||||
}
|
||||
|
||||
// V8:REQ_TRACK 开放态部分唯一约束(仅 PENDING/SENT;关闭态可并存)
|
||||
stmt.executeQuery(
|
||||
"SELECT indexdef FROM pg_indexes WHERE tablename = 'req_track' AND indexname = 'uq_req_open'",
|
||||
).use { rs ->
|
||||
assertTrue(rs.next(), "V8 必须建立开放态唯一索引 uq_req_open")
|
||||
val indexDef = rs.getString(1)
|
||||
assertTrue(indexDef.contains("UNIQUE"), "uq_req_open 必须是唯一索引:$indexDef")
|
||||
assertTrue(indexDef.contains("WHERE"), "uq_req_open 必须是仅约束开放态的部分索引:$indexDef")
|
||||
}
|
||||
stmt.executeUpdate(
|
||||
"INSERT INTO req_track (req_type, operation_day, sender, state, created_at) " +
|
||||
"VALUES ('RQFD-NONE', DATE '2026-09-12', 'RMS', 'PENDING', now())",
|
||||
)
|
||||
stmt.executeUpdate(
|
||||
"INSERT INTO req_track (req_type, operation_day, sender, state, created_at) " +
|
||||
"VALUES ('RQFD-NONE', DATE '2026-09-12', 'RMS', 'DONE', now())",
|
||||
)
|
||||
assertThrows(java.sql.SQLException::class.java) {
|
||||
stmt.executeUpdate(
|
||||
"INSERT INTO req_track (req_type, operation_day, sender, state, created_at) " +
|
||||
"VALUES ('RQFD-NONE', DATE '2026-09-12', 'RMS', 'SENT', now())",
|
||||
)
|
||||
}
|
||||
stmt.executeUpdate(
|
||||
"UPDATE req_track SET state = 'DONE' WHERE req_type = 'RQFD-NONE' AND state = 'PENDING'",
|
||||
)
|
||||
stmt.executeUpdate(
|
||||
"INSERT INTO req_track (req_type, operation_day, sender, state, created_at) " +
|
||||
"VALUES ('RQFD-NONE', DATE '2026-09-12', 'RMS', 'PENDING', now())",
|
||||
)
|
||||
|
||||
// V6:入队时间是超期判据 R 的比较对象,必须非空(received_at 则允许为 NULL)
|
||||
stmt.executeQuery(
|
||||
"SELECT is_nullable FROM information_schema.columns " +
|
||||
|
||||
Reference in New Issue
Block a user