test_db_migration_contract.py 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. from __future__ import annotations
  2. from pathlib import Path
  3. MIGRATION = Path("db/migrations/001_creation_knowledge_schema.sql")
  4. MIGRATIONS_DIR = Path("db/migrations")
  5. def _migration_sql() -> str:
  6. return MIGRATION.read_text(encoding="utf-8")
  7. def _all_migration_sql() -> str:
  8. return "\n".join(
  9. path.read_text(encoding="utf-8")
  10. for path in sorted(MIGRATIONS_DIR.glob("*.sql"))
  11. )
  12. def test_creation_knowledge_migration_declares_formal_business_tables():
  13. sql = _migration_sql()
  14. expected_tables = [
  15. "query_batches",
  16. "queries",
  17. "acquisition_runs",
  18. "acquisition_jobs",
  19. "candidate_items",
  20. "media_assets",
  21. "item_classifications",
  22. "decode_jobs",
  23. "decode_results",
  24. "knowledge_particles",
  25. "scope_results",
  26. "payload_drafts",
  27. "ingest_records",
  28. "contract_snapshots",
  29. ]
  30. assert "CREATE SCHEMA IF NOT EXISTS creation_knowledge" in sql
  31. assert "CREATE EXTENSION IF NOT EXISTS pgcrypto" in sql
  32. for table in expected_tables:
  33. assert f"CREATE TABLE IF NOT EXISTS creation_knowledge.{table}" in sql
  34. def test_migration_is_replayable_and_not_sqlite_shaped():
  35. sql = _migration_sql()
  36. assert "CREATE TABLE IF NOT EXISTS" in sql
  37. assert "CREATE INDEX IF NOT EXISTS" in sql
  38. assert "ON CONFLICT (version) DO UPDATE" in sql
  39. assert "CREATE OR REPLACE FUNCTION creation_knowledge.touch_updated_at" in sql
  40. assert "CREATE TRIGGER" in sql
  41. sqlite_only_tokens = ["AUTOINCREMENT", "PRAGMA", "sqlite_master", "WITHOUT ROWID"]
  42. assert not any(token in sql.upper() for token in sqlite_only_tokens)
  43. def test_migration_keeps_state_rows_traceable_and_resumable():
  44. sql = _all_migration_sql()
  45. for table in [
  46. "acquisition_runs",
  47. "acquisition_jobs",
  48. "candidate_items",
  49. "decode_jobs",
  50. "decode_results",
  51. "payload_drafts",
  52. "ingest_records",
  53. ]:
  54. assert f"creation_knowledge.{table}" in sql
  55. assert "status text NOT NULL" in sql
  56. assert "attempt_count integer NOT NULL DEFAULT 0" in sql
  57. assert "error_message text" in sql
  58. assert "metadata jsonb NOT NULL DEFAULT '{}'::jsonb" in sql
  59. assert "source_payload jsonb NOT NULL DEFAULT '{}'::jsonb" in sql
  60. assert "response_payload jsonb NOT NULL DEFAULT '{}'::jsonb" in sql
  61. assert "UNIQUE (run_id, query_id, platform)" in sql
  62. assert "idx_candidate_items_platform_item" in sql
  63. def test_candidate_items_unique_key_migration_is_declared():
  64. sql = _all_migration_sql()
  65. assert "ADD COLUMN IF NOT EXISTS unique_key text" in sql
  66. assert "idx_candidate_items_unique_key" in sql
  67. assert "WHERE unique_key IS NOT NULL" in sql
  68. assert "'002_candidate_items_unique_key'" in sql
  69. def test_candidate_items_content_mode_migration_is_declared():
  70. sql = _all_migration_sql()
  71. assert "ADD COLUMN IF NOT EXISTS content_mode text" in sql
  72. assert "idx_candidate_items_content_mode" in sql
  73. assert "video_post" in sql
  74. assert "image_post" in sql
  75. assert "article" in sql
  76. assert "unsupported" in sql
  77. assert "'003_candidate_items_content_mode'" in sql
  78. def test_candidate_items_body_text_migration_is_declared():
  79. sql = _all_migration_sql()
  80. assert "ADD COLUMN IF NOT EXISTS body_text text" in sql
  81. assert "source_payload #>> '{detail,data,data,body_text}'" in sql
  82. assert "LEFT(NULLIF(source_payload #>> '{detail,data,data,body_text}', ''), 50000)" in sql
  83. assert "'004_candidate_items_body_text'" in sql