106 lines
4.4 KiB
Python
106 lines
4.4 KiB
Python
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
import unittest
|
|
|
|
|
|
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
|
UP_PATH = PROJECT_ROOT / "database" / "017_daily_price_review.sql"
|
|
DOWN_PATH = PROJECT_ROOT / "database" / "017_daily_price_review.down.sql"
|
|
|
|
|
|
class DailyPriceReviewMigrationTests(unittest.TestCase):
|
|
def test_forward_migration_adds_only_review_lifecycle_and_manual_lineage(self) -> None:
|
|
sql = UP_PATH.read_text(encoding="utf-8")
|
|
lowered = sql.lower()
|
|
|
|
for required in (
|
|
"current_database() <> 'booking_test'",
|
|
"migrations 008 through 016 must be applied before 017",
|
|
"CREATE TABLE ingestion.daily_review_cases",
|
|
"CREATE TABLE ingestion.daily_review_items",
|
|
"CREATE TABLE ingestion.daily_review_events",
|
|
"processing_run_id bigint NOT NULL UNIQUE",
|
|
"daily_review_items_key_unique",
|
|
"'awaiting_review'",
|
|
"'review_required'",
|
|
"'recorded_review'",
|
|
"numeric(18,2)",
|
|
"'manual_review'",
|
|
"manual_override_sha256",
|
|
"manually_priced_rows",
|
|
"daily_versions_source_rule_override_unique",
|
|
"daily_review_items_mutability_guard",
|
|
"daily_review_cases_immutability_guard",
|
|
"daily_review_events_immutability_guard",
|
|
"PRICE_REVIEW_REQUIRED",
|
|
"PRICE_REVIEW_UPDATED",
|
|
"PRICE_REVIEW_FINALIZED",
|
|
"PRICE_REVIEW_CANCELLED",
|
|
"PRICE_REVIEW_GENERATION_FAILED",
|
|
"PRICE_REVIEW_FAILED",
|
|
"result_delivery_mode = 'artifact_callback'",
|
|
"result_delivery_mode = 'direct_mcp'",
|
|
):
|
|
with self.subTest(required=required):
|
|
self.assertIn(required, sql)
|
|
|
|
review_section = lowered[
|
|
lowered.index("create table ingestion.daily_review_cases") : lowered.index(
|
|
"alter table finance.daily_versions"
|
|
)
|
|
]
|
|
for forbidden in ("full_name", "confirmation_no", "res_comment", "trace_text", "remarks"):
|
|
with self.subTest(forbidden=forbidden):
|
|
self.assertNotIn(forbidden, review_section)
|
|
self.assertIn(
|
|
"GRANT SELECT, INSERT, UPDATE ON ingestion.daily_review_cases, ingestion.daily_review_items TO arr_app",
|
|
sql,
|
|
)
|
|
self.assertIn(
|
|
"GRANT SELECT, INSERT ON ingestion.daily_review_events TO arr_app",
|
|
sql,
|
|
)
|
|
self.assertNotIn("DROP SCHEMA", sql)
|
|
|
|
def test_down_migration_refuses_to_discard_review_or_manual_facts(self) -> None:
|
|
sql = DOWN_PATH.read_text(encoding="utf-8")
|
|
for required in (
|
|
"current_database() <> 'booking_test'",
|
|
"SELECT 1 FROM ingestion.daily_review_cases",
|
|
"review_case_id IS NOT NULL",
|
|
"manual_override_sha256 IS NOT NULL",
|
|
"run_status = 'awaiting_review'",
|
|
"attempt_status = 'review_required'",
|
|
"delivery_status = 'recorded_review'",
|
|
"refusing destructive rollback: daily review or manual-pricing facts exist",
|
|
"DROP TABLE ingestion.daily_review_events",
|
|
"DROP TABLE ingestion.daily_review_items",
|
|
"DROP TABLE ingestion.daily_review_cases",
|
|
"daily_review_events_immutability_guard",
|
|
"result_delivery_mode = 'artifact_callback'",
|
|
"result_delivery_mode = 'direct_mcp'",
|
|
):
|
|
with self.subTest(required=required):
|
|
self.assertIn(required, sql)
|
|
|
|
def test_followup_artifact_kind_migration_is_forward_only_after_manifest_facts(self) -> None:
|
|
root = Path(__file__).resolve().parents[1]
|
|
up = (root / "database" / "018_daily_review_manual_override_artifact.sql").read_text(
|
|
encoding="utf-8"
|
|
)
|
|
down = (
|
|
root / "database" / "018_daily_review_manual_override_artifact.down.sql"
|
|
).read_text(encoding="utf-8")
|
|
|
|
self.assertIn("ARR migration 017 must be applied first", up)
|
|
self.assertIn("'manual_override_json'", up)
|
|
self.assertIn("artifacts_artifact_kind_check", up)
|
|
self.assertIn("ARR migration 018 is already applied", up)
|
|
self.assertIn("artifact_kind = 'manual_override_json'", down)
|
|
self.assertIn("refusing rollback: immutable manual-override artifacts exist", down)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main(verbosity=2)
|