test(automation): require local PostgreSQL opt-in
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 2m26s
CD Pipeline / build-and-deploy (push) Failing after 8m50s
CD Pipeline / post-deploy-checks (push) Has been skipped

This commit is contained in:
ogt
2026-07-14 10:10:45 +08:00
parent e800d62682
commit efde0701e9
2 changed files with 40 additions and 3 deletions

View File

@@ -3,6 +3,7 @@
from __future__ import annotations from __future__ import annotations
import json import json
import os
from decimal import Decimal from decimal import Decimal
from types import SimpleNamespace from types import SimpleNamespace
from urllib.parse import parse_qsl, urlsplit from urllib.parse import parse_qsl, urlsplit
@@ -15,14 +16,29 @@ from sqlalchemy.ext.asyncio import create_async_engine
from src.jobs import backup_restore_legacy_backfill_job as backfill from src.jobs import backup_restore_legacy_backfill_job as backfill
from src.services import platform_operator_service from src.services import platform_operator_service
from tests.integration.conftest import DEV_DB_URL
def _explicit_local_test_database_url() -> str:
database_url = os.getenv("BACKUP_RESTORE_TEST_DATABASE_URL", "").strip()
if not database_url:
pytest.skip("BACKUP_RESTORE_TEST_DATABASE_URL is required for real PG tests")
parsed = urlsplit(
database_url.replace("postgresql+asyncpg://", "postgresql://", 1)
)
if parsed.hostname not in {None, "", "localhost", "127.0.0.1", "::1"}:
pytest.fail("backup/restore PG regression must target a local database")
database_name = parsed.path.rsplit("/", 1)[-1].strip().lower()
if not database_name or "prod" in database_name:
pytest.fail("backup/restore PG regression database name is unsafe")
return database_url
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_backfill_run_inserts_satisfy_no_default_cost_column() -> None: async def test_backfill_run_inserts_satisfy_no_default_cost_column() -> None:
"""The deployed schema has cost_usd NOT NULL without a column default.""" """The deployed schema has cost_usd NOT NULL without a column default."""
engine = create_async_engine(DEV_DB_URL, echo=False) engine = create_async_engine(_explicit_local_test_database_url(), echo=False)
cursor_run_id = UUID("7458761b-65c6-59d2-92b4-822725ba2724") cursor_run_id = UUID("7458761b-65c6-59d2-92b4-822725ba2724")
item_run_id = UUID("3f6e4e64-4510-57f8-bf2e-c59bb1ed72d1") item_run_id = UUID("3f6e4e64-4510-57f8-bf2e-c59bb1ed72d1")
try: try:
@@ -136,7 +152,7 @@ async def test_direct_readback_rewrites_ssl_and_keeps_rls_context_in_transaction
) -> None: ) -> None:
"""Run the fallback SQL on PostgreSQL behind an RLS-equivalent view.""" """Run the fallback SQL on PostgreSQL behind an RLS-equivalent view."""
sqlalchemy_url = DEV_DB_URL sqlalchemy_url = _explicit_local_test_database_url()
if not any( if not any(
key in {"ssl", "sslmode"} key in {"ssl", "sslmode"}
for key, _value in parse_qsl(urlsplit(sqlalchemy_url).query) for key, _value in parse_qsl(urlsplit(sqlalchemy_url).query)

View File

@@ -1,6 +1,7 @@
from __future__ import annotations from __future__ import annotations
import json import json
import re
from datetime import UTC, datetime, timedelta from datetime import UTC, datetime, timedelta
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any
@@ -203,6 +204,26 @@ def test_cursor_never_reports_completed_when_any_item_failed() -> None:
) == ("blocked_accounting_drift_fail_closed", "failed") ) == ("blocked_accounting_drift_fail_closed", "failed")
def test_real_pg_regression_requires_explicit_local_opt_in() -> None:
source_path = (
Path(__file__).parent
/ "integration"
/ "test_backup_restore_prod_db_regressions.py"
)
source = source_path.read_text(encoding="utf-8")
assert "BACKUP_RESTORE_TEST_DATABASE_URL" in source
assert "tests.integration.conftest" not in source
assert "DEV_DB_URL" not in source
assert "pytest.skip" in source
assert not re.search(
r"postgresql(?:\+asyncpg)?://[^/\s:@]+:[^@\s/]+@",
source,
)
ipv4_literals = set(re.findall(r"\b(?:\d{1,3}\.){3}\d{1,3}\b", source))
assert ipv4_literals <= {"127.0.0.1"}
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_116_rows_progress_through_two_bounded_durable_cohorts( async def test_116_rows_progress_through_two_bounded_durable_cohorts(
monkeypatch: pytest.MonkeyPatch, monkeypatch: pytest.MonkeyPatch,