agriflow-api / tests /test_db_loader.py
masterAAA123's picture
Space deploy v5: orphan snapshot of main, zero binary files
b81a86b
Raw
History Blame Contribute Delete
7.41 kB
"""
Tests for db/db_loader.py and DATA_BACKEND env-switch in server.py.
These tests run fully offline — no Supabase/Postgres connection is made.
Coverage:
(a) Import: db.db_loader is importable without any env vars set.
(b) No-env error: load_all() raises RuntimeError with a clear message
when SUPABASE_DB_URL is absent.
(c) CSV default: server._load_data_backend() returns CSV data when
DATA_BACKEND is not set (or is 'csv').
(d) Backend dispatch: DATA_BACKEND=postgres routes to db_loader.load_all().
(e) Return-key contract: both backends return the same 6 top-level keys.
"""
from __future__ import annotations
import os
import sys
# Ensure project root on path (same pattern as other tests)
_HERE = os.path.dirname(os.path.abspath(__file__))
_ROOT = os.path.dirname(_HERE)
if _ROOT not in sys.path:
sys.path.insert(0, _ROOT)
import pytest
# =============================================================================
# (a) Import succeeds without any env vars
# =============================================================================
class TestImport:
def test_db_loader_importable(self):
"""db.db_loader must be importable regardless of env state."""
import db.db_loader as m # noqa: F401
assert hasattr(m, "load_all"), "load_all function must exist"
def test_db_package_importable(self):
"""db/__init__.py must be importable."""
import db # noqa: F401
# =============================================================================
# (b) load_all() raises RuntimeError when SUPABASE_DB_URL is absent
# =============================================================================
class TestNoEnvRaisesRuntimeError:
def test_missing_env_raises(self, monkeypatch):
"""load_all() must raise RuntimeError with a descriptive message."""
# Guarantee the env var is absent for this test
monkeypatch.delenv("SUPABASE_DB_URL", raising=False)
from db.db_loader import load_all
with pytest.raises(RuntimeError) as exc_info:
load_all()
msg = str(exc_info.value)
assert "SUPABASE_DB_URL" in msg, (
"RuntimeError should name the missing env var so the operator knows what to set"
)
def test_empty_string_env_raises(self, monkeypatch):
"""An empty SUPABASE_DB_URL must be treated the same as absent."""
monkeypatch.setenv("SUPABASE_DB_URL", "")
from db.db_loader import load_all
with pytest.raises(RuntimeError) as exc_info:
load_all()
assert "SUPABASE_DB_URL" in str(exc_info.value)
# =============================================================================
# (c) CSV default path — server._load_data_backend() uses CSV when
# DATA_BACKEND is unset or 'csv'
# =============================================================================
EXPECTED_KEYS = {"kabupaten", "komoditas", "surplus", "deficit", "weather", "historical_prices"}
class TestCsvDefaultPath:
def test_unset_data_backend_uses_csv(self, monkeypatch):
"""Without DATA_BACKEND env, _load_data_backend() returns CSV data."""
monkeypatch.delenv("DATA_BACKEND", raising=False)
# Re-import so the function picks up clean env
import importlib
import whatsapp_bot.server as srv
importlib.reload(srv)
data = srv._load_data_backend()
assert isinstance(data, dict)
assert set(data.keys()) == EXPECTED_KEYS
def test_explicit_csv_data_backend(self, monkeypatch):
"""DATA_BACKEND=csv must use CSV loader."""
monkeypatch.setenv("DATA_BACKEND", "csv")
import importlib
import whatsapp_bot.server as srv
importlib.reload(srv)
data = srv._load_data_backend()
assert isinstance(data, dict)
assert set(data.keys()) == EXPECTED_KEYS
def test_csv_data_has_content(self, monkeypatch):
"""CSV path must load at least one kabupaten and one komoditas."""
monkeypatch.delenv("DATA_BACKEND", raising=False)
import importlib
import whatsapp_bot.server as srv
importlib.reload(srv)
data = srv._load_data_backend()
assert len(data["kabupaten"]) > 0, "Expected kabupaten rows from CSV"
assert len(data["komoditas"]) > 0, "Expected komoditas rows from CSV"
assert len(data["surplus"]) > 0, "Expected surplus nodes from CSV"
assert len(data["deficit"]) > 0, "Expected deficit nodes from CSV"
# =============================================================================
# (d) Postgres backend routes to db_loader.load_all (verified via mock)
# =============================================================================
class TestPostgresBackendDispatch:
def test_postgres_backend_calls_db_loader(self, monkeypatch):
"""DATA_BACKEND=postgres must call db.db_loader.load_all, not CSV loader."""
monkeypatch.setenv("DATA_BACKEND", "postgres")
monkeypatch.setenv("SUPABASE_DB_URL", "postgresql+psycopg2://fake:fake@localhost:5432/fake")
call_log: list[str] = []
def fake_load_all():
call_log.append("db_loader.load_all called")
# Return the same shape as the real loader so downstream code is happy
from sample_data.loader import load_all_sample_data
return load_all_sample_data()
import importlib
import db.db_loader
monkeypatch.setattr(db.db_loader, "load_all", fake_load_all)
import whatsapp_bot.server as srv
importlib.reload(srv)
# Now call the dispatch function — it should route to our mock
data = srv._load_data_backend()
assert "db_loader.load_all called" in call_log, (
"DATA_BACKEND=postgres did not call db.db_loader.load_all"
)
assert set(data.keys()) == EXPECTED_KEYS
def test_postgres_backend_without_url_raises(self, monkeypatch):
"""DATA_BACKEND=postgres + missing SUPABASE_DB_URL => RuntimeError."""
monkeypatch.setenv("DATA_BACKEND", "postgres")
monkeypatch.delenv("SUPABASE_DB_URL", raising=False)
import importlib
import whatsapp_bot.server as srv
importlib.reload(srv)
with pytest.raises(RuntimeError) as exc_info:
srv._load_data_backend()
assert "SUPABASE_DB_URL" in str(exc_info.value)
# =============================================================================
# (e) Return-key contract — both backends expose identical top-level keys
# =============================================================================
class TestReturnKeyContract:
def test_csv_keys_match_contract(self, monkeypatch):
monkeypatch.delenv("DATA_BACKEND", raising=False)
import importlib
import whatsapp_bot.server as srv
importlib.reload(srv)
data = srv._load_data_backend()
assert set(data.keys()) == EXPECTED_KEYS
def test_db_loader_signature_documented(self):
"""db_loader.load_all docstring must reference all 6 keys."""
from db.db_loader import load_all
doc = load_all.__doc__ or ""
for key in EXPECTED_KEYS:
assert key in doc, (
f"Key '{key}' missing from db_loader.load_all docstring — "
"keep docs aligned with the return contract"
)