B-43: 双边流水归并与规范事件层——迁移5、匹配引擎、个人过账映射与事件API
This commit is contained in:
+127
-4
@@ -41,7 +41,7 @@ class PersistenceTestCase(unittest.TestCase):
|
||||
class MigrationTests(PersistenceTestCase):
|
||||
def test_migrate_creates_schema_and_is_idempotent(self) -> None:
|
||||
first = applied_versions(self.connection)
|
||||
self.assertEqual([1, 2, 3, 4], first)
|
||||
self.assertEqual([1, 2, 3, 4, 5], first)
|
||||
self.assertEqual([], migrate(self.connection))
|
||||
self.assertEqual(first, applied_versions(self.connection))
|
||||
tables = {
|
||||
@@ -65,19 +65,142 @@ class MigrationTests(PersistenceTestCase):
|
||||
"sessions",
|
||||
"login_attempts",
|
||||
"audit_log",
|
||||
"personal_transit_mappings",
|
||||
"canonical_transfer_events",
|
||||
"transfer_match_decisions",
|
||||
"transfer_decision_observations",
|
||||
"transfer_decision_participants",
|
||||
"transfer_match_candidates",
|
||||
"current_transfer_decisions",
|
||||
"transfer_observation_claims",
|
||||
"schema_migrations",
|
||||
):
|
||||
self.assertIn(table, tables)
|
||||
|
||||
def test_rollback_removes_schema_and_forward_rebuilds_it(self) -> None:
|
||||
self.assertEqual([4, 3, 2, 1], rollback(self.connection, 0))
|
||||
self.assertEqual([5, 4, 3, 2, 1], rollback(self.connection, 0))
|
||||
self.assertEqual([], applied_versions(self.connection))
|
||||
remaining = self.connection.execute(
|
||||
"SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'source_rows'"
|
||||
).fetchone()
|
||||
self.assertIsNone(remaining)
|
||||
self.assertEqual([1, 2, 3, 4], migrate(self.connection))
|
||||
self.assertEqual([1, 2, 3, 4], applied_versions(self.connection))
|
||||
self.assertEqual([1, 2, 3, 4, 5], migrate(self.connection))
|
||||
self.assertEqual([1, 2, 3, 4, 5], applied_versions(self.connection))
|
||||
|
||||
def test_rollback_to_4_keeps_bank_evidence_and_drops_event_layer(self) -> None:
|
||||
self.import_sample()
|
||||
row_count = self.connection.execute(
|
||||
"SELECT COUNT(*) AS n FROM source_rows"
|
||||
).fetchone()["n"]
|
||||
self.assertGreater(row_count, 0)
|
||||
self.assertEqual([5], rollback(self.connection, 4))
|
||||
# The pre-migration evidence and schema are untouched.
|
||||
self.assertEqual(
|
||||
row_count,
|
||||
self.connection.execute("SELECT COUNT(*) AS n FROM source_rows").fetchone()["n"],
|
||||
)
|
||||
self.assertEqual(
|
||||
"parsed",
|
||||
self.connection.execute(
|
||||
"SELECT status FROM import_batches LIMIT 1"
|
||||
).fetchone()["status"],
|
||||
)
|
||||
remaining = self.connection.execute(
|
||||
"SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'transfer_match_decisions'"
|
||||
).fetchone()
|
||||
self.assertIsNone(remaining)
|
||||
|
||||
def test_event_layer_views_exist_after_migration(self) -> None:
|
||||
view = self.connection.execute(
|
||||
"SELECT name FROM sqlite_master WHERE type = 'view' AND name = 'eligible_intercompany_events'"
|
||||
).fetchone()
|
||||
self.assertIsNotNone(view)
|
||||
|
||||
def test_decision_log_immutability_triggers(self) -> None:
|
||||
self.import_sample()
|
||||
source_row_id = self.connection.execute(
|
||||
"SELECT id FROM source_rows LIMIT 1"
|
||||
).fetchone()["id"]
|
||||
with self.connection:
|
||||
self.connection.execute(
|
||||
"INSERT INTO canonical_transfer_events (lifecycle, created_at) VALUES ('active', ?)",
|
||||
(utc_now(),),
|
||||
)
|
||||
event_id = self.connection.execute(
|
||||
"SELECT id FROM canonical_transfer_events LIMIT 1"
|
||||
).fetchone()["id"]
|
||||
self.connection.execute(
|
||||
"""
|
||||
INSERT INTO transfer_match_decisions (
|
||||
event_id, revision, classification, pairing, mode, rule_version,
|
||||
locked, created_at
|
||||
) VALUES (?, 1, 'unresolved', 'not_applicable', 'auto', 'transfer-match-v1', 0, ?)
|
||||
""",
|
||||
(event_id, utc_now()),
|
||||
)
|
||||
decision_id = self.connection.execute(
|
||||
"SELECT id FROM transfer_match_decisions LIMIT 1"
|
||||
).fetchone()["id"]
|
||||
self.connection.execute(
|
||||
"""
|
||||
INSERT INTO transfer_match_candidates (
|
||||
decision_id, source_row_id, rule_tier, rule_version, created_at
|
||||
) VALUES (?, ?, 'R1', 'transfer-match-v1', ?)
|
||||
""",
|
||||
(decision_id, source_row_id, utc_now()),
|
||||
)
|
||||
for statement in (
|
||||
"UPDATE transfer_match_decisions SET classification = 'external'",
|
||||
"DELETE FROM transfer_match_decisions",
|
||||
"UPDATE transfer_match_candidates SET rule_tier = 'M1'",
|
||||
"DELETE FROM transfer_match_candidates",
|
||||
"DELETE FROM canonical_transfer_events",
|
||||
):
|
||||
with self.subTest(statement=statement):
|
||||
with self.assertRaises(sqlite3.IntegrityError):
|
||||
self.connection.execute(statement)
|
||||
self.connection.rollback()
|
||||
|
||||
def test_observation_claims_source_row_is_unique(self) -> None:
|
||||
self.import_sample()
|
||||
source_row_id = self.connection.execute(
|
||||
"SELECT id FROM source_rows LIMIT 1"
|
||||
).fetchone()["id"]
|
||||
with self.connection:
|
||||
self.connection.execute(
|
||||
"INSERT INTO canonical_transfer_events (lifecycle, created_at) VALUES ('active', ?)",
|
||||
(utc_now(),),
|
||||
)
|
||||
event_id = self.connection.execute(
|
||||
"SELECT id FROM canonical_transfer_events LIMIT 1"
|
||||
).fetchone()["id"]
|
||||
self.connection.execute(
|
||||
"""
|
||||
INSERT INTO transfer_match_decisions (
|
||||
event_id, revision, classification, pairing, mode, locked, created_at
|
||||
) VALUES (?, 1, 'unresolved', 'not_applicable', 'auto', 0, ?)
|
||||
""",
|
||||
(event_id, utc_now()),
|
||||
)
|
||||
decision_id = self.connection.execute(
|
||||
"SELECT id FROM transfer_match_decisions LIMIT 1"
|
||||
).fetchone()["id"]
|
||||
self.connection.execute(
|
||||
"""
|
||||
INSERT INTO transfer_observation_claims (source_row_id, event_id, decision_id)
|
||||
VALUES (?, ?, ?)
|
||||
""",
|
||||
(source_row_id, event_id, decision_id),
|
||||
)
|
||||
with self.assertRaises(sqlite3.IntegrityError):
|
||||
self.connection.execute(
|
||||
"""
|
||||
INSERT INTO transfer_observation_claims (source_row_id, event_id, decision_id)
|
||||
VALUES (?, ?, ?)
|
||||
""",
|
||||
(source_row_id, event_id, decision_id),
|
||||
)
|
||||
self.connection.rollback()
|
||||
|
||||
|
||||
class ImportPersistenceTests(PersistenceTestCase):
|
||||
|
||||
Reference in New Issue
Block a user