teleo-infrastructure/tests/test_detect_vps_gcp_source_drift.py

229 lines
7.7 KiB
Python

import argparse
import copy
import hashlib
import json
from pathlib import Path
import pytest
from ops import detect_vps_gcp_source_drift as detector
def manifest(*, row_count: int = 3, rowset_md5: str = "1" * 32) -> dict:
singleton = {kind: {"kind": kind, "items": []} for kind in detector.SINGLETON_KINDS if kind != "identity"}
singleton["identity"] = {
"kind": "identity",
"database": "teleo",
"transaction_read_only": "on",
"server_version_num": 160000,
"server_address": "10.0.0.1",
"server_port": 5432,
"ssl": True,
"ssl_version": "TLSv1.3",
}
return {
"singleton": singleton,
"tables": {
"public.claims": {
"kind": "table",
"schema": "public",
"table": "claims",
"row_count": row_count,
"rowset_md5": rowset_md5,
}
},
"performance": {},
}
def observation(label: str, value: dict | None = None) -> dict:
selected = copy.deepcopy(value or manifest())
return {
"schema": detector.OBSERVATION_SCHEMA,
"label": label,
"status": "observed_current",
"observed_at_utc": "2026-07-15T20:00:00Z",
"route": {
"hostname": f"{label}-host",
"service": f"{label}.service",
"runtime_commit": "a" * 40,
"expected_database": "teleo",
},
"database_identity": selected["singleton"]["identity"],
"fingerprint": detector.manifest_fingerprint(selected),
"manifest": selected,
"cleanup": {"remote_files_created": False, "local_temp_files_created": False},
}
def test_identical_current_observations_pass() -> None:
receipt = detector.evaluate_observations(
observation("vps"),
observation("gcp"),
generated_at_utc="2026-07-15T20:01:00Z",
)
assert receipt["status"] == "identical"
assert receipt["comparison"]["state"] == "identical"
assert receipt["fallback_used"] is False
assert receipt["historical_receipt_substituted"] is False
assert receipt["database_mutated"] is False
assert "manifest" not in receipt["observations"]["vps"]
def test_content_drift_fails_and_names_exact_table_mismatch() -> None:
gcp_manifest = manifest(row_count=4, rowset_md5="2" * 32)
receipt = detector.evaluate_observations(observation("vps"), observation("gcp", gcp_manifest))
assert receipt["status"] == "drift"
assert receipt["comparison"]["content_identical"] is False
assert "table_content_mismatches=1" in receipt["comparison"]["problems"]
assert receipt["comparison"]["details"]["table_mismatches"] == [
{
"table": "public.claims",
"mismatch": {
"row_count": {"source": 3, "target": 4},
"rowset_md5": {"source": "1" * 32, "target": "2" * 32},
},
}
]
def test_unreachable_gcp_is_incomplete_without_vps_substitution() -> None:
gcp = {
"schema": detector.OBSERVATION_SCHEMA,
"label": "gcp",
"status": "unreachable",
"failure_class": "probe_timeout",
"error": "gcp probe exceeded 30s",
"manifest": None,
"route": None,
}
receipt = detector.evaluate_observations(observation("vps"), gcp)
assert receipt["status"] == "incomplete"
assert receipt["unavailable_targets"] == ["gcp"]
assert receipt["comparison"] is None
assert receipt["observations"]["gcp"]["route"] is None
assert receipt["observations"]["gcp"] != receipt["observations"]["vps"]
assert receipt["fallback_used"] is False
assert receipt["historical_receipt_substituted"] is False
def test_probe_timeout_preserves_exact_expected_target(monkeypatch: pytest.MonkeyPatch) -> None:
expected = {
"transport": "teleo-gcp-staging",
"database_endpoint": "10.61.0.3:5432",
"database": "teleo_canonical",
}
def timeout(*args, **kwargs):
raise detector.subprocess.TimeoutExpired(args[0], kwargs["timeout"])
monkeypatch.setattr(detector.subprocess, "run", timeout)
result = detector.collect_endpoint(
label="gcp",
command=["ssh", "teleo-gcp-staging", "--", "true"],
manifest_sql=b"select 1",
timeout=30,
expected_target=expected,
)
assert result["status"] == "unreachable"
assert result["failure_class"] == "probe_timeout"
assert result["expected_target"] == expected
assert result["route"] is None
def test_parser_rejects_non_readonly_or_wrong_database(monkeypatch: pytest.MonkeyPatch) -> None:
selected = manifest()
lines = [json.dumps(row) for row in selected["singleton"].values()]
lines.extend(json.dumps(row) for row in selected["tables"].values())
route = {
"runtime_commit": "a" * 40,
"expected_database": "different",
}
stdout = (detector.ROUTE_PREFIX + json.dumps(route) + "\n" + "\n".join(lines)).encode()
monkeypatch.setattr(
detector.subprocess,
"run",
lambda *args, **kwargs: detector.subprocess.CompletedProcess(args[0], 0, stdout, b""),
)
result = detector.collect_endpoint(
label="gcp",
command=["ssh", "target", "--", "true"],
manifest_sql=b"select 1",
timeout=30,
expected_target={"database_endpoint": "10.61.0.3:5432", "database": "different"},
)
assert result["status"] == "invalid"
assert result["failure_class"] == "invalid_probe_receipt"
assert "does not match explicit target" in result["error"]
def test_live_runs_both_independent_collectors_and_returns_incomplete(tmp_path: Path) -> None:
manifest_path = tmp_path / "manifest.sql"
manifest_bytes = b"reviewed manifest"
manifest_path.write_bytes(manifest_bytes)
key = tmp_path / "key"
key.write_text("not-a-real-key")
called = []
def fake_collector(**kwargs):
called.append(kwargs["label"])
if kwargs["label"] == "vps":
return observation("vps")
return {
"schema": detector.OBSERVATION_SCHEMA,
"label": "gcp",
"status": "unreachable",
"failure_class": "transport_or_remote_probe_failed",
"error": "target unavailable",
"manifest": None,
"route": None,
}
monkeypatch = pytest.MonkeyPatch()
monkeypatch.setattr(detector, "REVIEWED_MANIFEST_SQL_SHA256", hashlib.sha256(manifest_bytes).hexdigest())
args = argparse.Namespace(
output=tmp_path / "output.json",
manifest=manifest_path,
connect_timeout=3,
probe_timeout=30,
vps_ssh_target="root@127.0.0.1",
vps_ssh_key=key,
vps_container="teleo-pg",
vps_database="teleo",
vps_database_user="postgres",
vps_service="leoclean-gateway.service",
gcp_ssh_target="teleo-gcp-staging",
gcp_host="10.61.0.3",
gcp_database="teleo_restore",
gcp_database_user="postgres",
gcp_project="teleo-501523",
gcp_secret="gcp-teleo-pgvector-standby-postgres-password",
gcp_service="leoclean-gcp-prod-parallel.service",
)
try:
receipt, exit_code = detector.run_live(args, collector=fake_collector)
finally:
monkeypatch.undo()
assert sorted(called) == ["gcp", "vps"]
assert receipt["status"] == "incomplete"
assert receipt["manifest_sql_sha256"] == hashlib.sha256(manifest_bytes).hexdigest()
assert exit_code == 2
def test_sanitizer_redacts_connection_secrets() -> None:
error = detector.sanitize_error(
"fatal password=hunter2 postgresql://user:pw@db.internal/teleo",
target="db.internal",
)
assert "hunter2" not in error
assert "user:pw" not in error
assert "[REDACTED]" in error