Files
ThothII/harness/tests/test_dwh_adapters.py
T

127 lines
4.6 KiB
Python

import pytest
from sqlalchemy.exc import OperationalError
from tht.config import DatabaseConfig, RestConfig
from tht.execute import ExecutionError
from tht.ports import DistinctValues, DwhAdapter
from tht.rest.client import RestError
def postgres_factory():
from tht.adapters.dwh import PostgresDwhAdapter
return PostgresDwhAdapter(
DatabaseConfig(database="analytics", schema="dw", user="reader", password="secret")
)
def rest_factory():
from tht.adapters.dwh import ThothRestDwhAdapter
database = DatabaseConfig(
database="analytics",
schema="dw",
user="unused",
password="unused",
transport="rest",
)
return ThothRestDwhAdapter(
database,
RestConfig(base_url="https://dwh.example.test", api_key="secret"),
)
@pytest.mark.parametrize("factory", [postgres_factory, rest_factory])
def test_adapter_rejects_write_sql_without_using_transport(factory):
with pytest.raises(ExecutionError, match="read-only enforcement"):
factory().run_query("delete from fact_sales", limit=10)
@pytest.mark.parametrize("factory", [postgres_factory, rest_factory])
def test_adapter_satisfies_dwh_protocol(factory):
adapter = factory()
assert isinstance(adapter, DwhAdapter)
assert adapter.capabilities.introspection is True
@pytest.mark.parametrize("factory", [postgres_factory, rest_factory])
def test_run_query_requires_explicit_positive_limit(factory):
adapter = factory()
with pytest.raises(TypeError):
adapter.run_query("select 1")
with pytest.raises(ValueError, match="positive"):
adapter.run_query("select 1", limit=0)
def test_postgres_sampling_delegates_to_paired_sampling_functions(monkeypatch):
adapter = postgres_factory()
calls = []
expected = DistinctValues(values=["A"], truncated=True)
monkeypatch.setattr(
"tht.adapters.dwh.postgres.sampling.sample_column",
lambda engine, schema, table, column, *, limit: calls.append(
(engine, schema, table, column, limit)
)
or ["A", "B"],
)
monkeypatch.setattr(
"tht.adapters.dwh.postgres.sampling.distinct_values",
lambda engine, schema, table, column: expected,
)
assert adapter.sample_column("sales", "region", limit=2) == ["A", "B"]
assert calls == [(adapter._engine, "dw", "sales", "region", 2)]
assert adapter.distinct_values("sales", "region") is expected
def test_rest_sampling_delegates_and_translates_transport_errors(monkeypatch):
adapter = rest_factory()
expected = DistinctValues(values=["A", "B"], truncated=False)
monkeypatch.setattr(
"tht.adapters.dwh.thoth_rest.sampling.sample_column_rest",
lambda client, schema, table, column, *, limit: ["A", "B"],
)
monkeypatch.setattr(
"tht.adapters.dwh.thoth_rest.sampling.distinct_values_rest",
lambda client, schema, table, column: expected,
)
assert adapter.sample_column("sales", "region", limit=2) == ["A", "B"]
assert adapter.distinct_values("sales", "region") is expected
def fail(*args, **kwargs):
raise RestError("transport failed")
monkeypatch.setattr("tht.adapters.dwh.thoth_rest.sampling.sample_column_rest", fail)
monkeypatch.setattr("tht.adapters.dwh.thoth_rest.sampling.distinct_values_rest", fail)
with pytest.raises(ExecutionError, match="transport failed"):
adapter.sample_column("sales", "region", limit=2)
with pytest.raises(ExecutionError, match="transport failed"):
adapter.distinct_values("sales", "region")
def test_rest_distinct_values_reports_transport_truncation():
from tht.db.sampling import distinct_values_rest
class Client:
def top_values(self, schema, table, column, limit):
assert (schema, table, column, limit) == ("dw", "sales", "region", 3)
return [{"value": "A"}, {"value": "B"}, {"value": "C"}]
result = distinct_values_rest(Client(), "dw", "sales", "region", max_values=2)
assert result == DistinctValues(values=["A", "B"], truncated=True)
def test_postgres_health_only_normalizes_database_errors(monkeypatch):
adapter = postgres_factory()
database_error = OperationalError("select 1", {}, Exception("offline"))
monkeypatch.setattr("tht.adapters.dwh.postgres.ping", lambda engine: (_ for _ in ()).throw(database_error))
assert adapter.health().ok is False
monkeypatch.setattr(
"tht.adapters.dwh.postgres.ping",
lambda engine: (_ for _ in ()).throw(ValueError("programming bug")),
)
with pytest.raises(ValueError, match="programming bug"):
adapter.health()