114 lines
4.7 KiB
Python
114 lines
4.7 KiB
Python
from __future__ import annotations
|
|
|
|
import re
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
|
|
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
|
UP_PATH = PROJECT_ROOT / "database" / "010_mcp_result_ingestion.sql"
|
|
DOWN_PATH = PROJECT_ROOT / "database" / "010_mcp_result_ingestion.down.sql"
|
|
|
|
|
|
class McpResultIngestionMigrationTests(unittest.TestCase):
|
|
@classmethod
|
|
def setUpClass(cls) -> None:
|
|
cls.up = UP_PATH.read_text(encoding="utf-8")
|
|
cls.down = DOWN_PATH.read_text(encoding="utf-8")
|
|
cls.up_lower = cls.up.lower()
|
|
cls.down_lower = cls.down.lower()
|
|
|
|
def test_up_is_additive_target_guarded_and_requires_mvp_v1(self) -> None:
|
|
self.assertIn("current_database() <> 'booking_test'", self.up)
|
|
self.assertIn("to_regclass('ingestion.processing_runs')", self.up)
|
|
self.assertIn("to_regclass('ingestion.processing_attempts')", self.up)
|
|
self.assertIn("to_regclass('finance.daily_versions')", self.up)
|
|
self.assertNotIn("drop schema", self.up_lower)
|
|
self.assertNotIn("truncate ", self.up_lower)
|
|
self.assertNotIn("delete from", self.up_lower)
|
|
|
|
def test_up_creates_attempt_bound_direct_submission_ledger(self) -> None:
|
|
for relation in (
|
|
"ingestion.result_submission_grants",
|
|
"ingestion.result_submissions",
|
|
):
|
|
self.assertIn(f"create table {relation}", self.up_lower)
|
|
self.assertNotIn("result_submission_batches", self.up_lower)
|
|
|
|
self.assertIn(
|
|
"foreign key (attempt_id, processing_run_id)",
|
|
self.up_lower,
|
|
)
|
|
self.assertIn("unique (attempt_id)", self.up_lower)
|
|
self.assertIn("interval '30 minutes'", self.up_lower)
|
|
self.assertIn(
|
|
"consumed_at between created_at and expires_at",
|
|
self.up_lower,
|
|
)
|
|
self.assertNotIn("write_token_sha256", self.up_lower)
|
|
self.assertNotIn("password", self.up_lower)
|
|
self.assertNotIn("access_key", self.up_lower)
|
|
|
|
def test_same_payload_hash_is_indexed_but_not_unique(self) -> None:
|
|
self.assertIn(
|
|
"create index result_submissions_payload_sha256_idx",
|
|
self.up_lower,
|
|
)
|
|
self.assertNotRegex(
|
|
self.up_lower,
|
|
re.compile(r"unique\s*\([^)]*payload_sha256", re.DOTALL),
|
|
)
|
|
self.assertIn("same xml/result may be processed repeatedly", self.up_lower)
|
|
|
|
def test_single_payload_is_bounded_counted_and_purged_at_terminal(self) -> None:
|
|
self.assertIn("payload_byte_size between 2 and 3145728", self.up_lower)
|
|
self.assertIn("payload_json ? 'records'", self.up_lower)
|
|
self.assertIn(
|
|
"jsonb_array_length(payload_json -> 'records') = record_count",
|
|
self.up_lower,
|
|
)
|
|
self.assertIn("submission_status = 'received'", self.up_lower)
|
|
self.assertIn("payload_json is not null", self.up_lower)
|
|
self.assertIn("payload_json is null", self.up_lower)
|
|
self.assertIn("payload_purged_at is not null", self.up_lower)
|
|
self.assertIn("submission_status <> 'expired'", self.up_lower)
|
|
self.assertIn("finished_at >= expires_at", self.up_lower)
|
|
self.assertNotIn("expected_batch_count", self.up_lower)
|
|
self.assertNotIn("received_batch_count", self.up_lower)
|
|
|
|
def test_existing_artifact_contract_is_preserved_as_default(self) -> None:
|
|
self.assertGreaterEqual(
|
|
self.up_lower.count("default 'artifact_callback'"),
|
|
2,
|
|
)
|
|
self.assertIn("result_delivery_mode = 'direct_mcp'", self.up_lower)
|
|
self.assertIn("result_artifact_id is null", self.up_lower)
|
|
self.assertIn("daily_report_artifact_id is null", self.up_lower)
|
|
self.assertIn("daily_report_artifact_id is not null", self.up_lower)
|
|
|
|
def test_down_refuses_data_and_restores_008_constraints(self) -> None:
|
|
self.assertIn("current_database() <> 'booking_test'", self.down)
|
|
self.assertIn(
|
|
"refusing rollback: migration 010 direct MCP state exists",
|
|
self.down,
|
|
)
|
|
self.assertIn("exists (select 1 from ingestion.result_submissions)", self.down_lower)
|
|
self.assertNotIn("result_submission_batches", self.down_lower)
|
|
self.assertLess(
|
|
self.down_lower.index("drop table ingestion.result_submissions"),
|
|
self.down_lower.index("drop table ingestion.result_submission_grants"),
|
|
)
|
|
self.assertIn("drop column result_delivery_mode", self.down_lower)
|
|
self.assertIn(
|
|
"add constraint processing_runs_terminal_shape check",
|
|
self.down_lower,
|
|
)
|
|
self.assertIn(
|
|
"add constraint daily_versions_status_shape check",
|
|
self.down_lower,
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main(verbosity=2)
|