osint-dashboard/tests/conftest.py

112 lines
3.2 KiB
Python
Raw Permalink Normal View History

"""Shared test fixtures.
Unit tests (parsing/mapping) run anywhere. DB-backed tests (idempotency, API)
are marked `integration` and auto-skip when the test database is unreachable
set DB_HOST/DB_PORT/DB_USER/DB_PASSWORD/DB_NAME to point at a TimescaleDB+PostGIS
instance (e.g. the `localhost/osint-dashboard-pg:test` image) to run them.
"""
from __future__ import annotations
import os
import sys
from pathlib import Path
import pytest
# Make the app package importable from tests (repo root/app).
APP_DIR = Path(__file__).resolve().parent.parent / "app"
if str(APP_DIR) not in sys.path:
sys.path.insert(0, str(APP_DIR))
# Point the app's config at the test database BEFORE any app module is imported
# (database.py builds DATABASE_URL from env at import time).
os.environ.setdefault("DB_HOST", "127.0.0.1")
os.environ.setdefault("DB_PORT", "55432")
os.environ.setdefault("DB_USER", "osint")
os.environ.setdefault("DB_PASSWORD", "osint")
os.environ.setdefault("DB_NAME", "osint_data")
# Each test opens a fresh event loop (asyncio.run); a pooled connection can't be
# reused across loops, so force a NullPool (new connection per session).
os.environ.setdefault("DB_NULL_POOL", "1")
import asyncpg # noqa: E402
pytestmark = []
def _db_reachable() -> bool:
try:
import asyncio
async def _ping() -> bool:
try:
conn = await asyncpg.connect(
host=os.environ["DB_HOST"],
port=int(os.environ["DB_PORT"]),
user=os.environ["DB_USER"],
password=os.environ["DB_PASSWORD"],
database=os.environ["DB_NAME"],
timeout=3,
)
await conn.close()
return True
except Exception:
return False
return asyncio.run(_ping())
except Exception:
return False
requires_db = pytest.mark.skipif(
not _db_reachable(),
reason="test database unreachable (set DB_* env or start the PG container)",
)
@pytest.fixture()
def clean_fires():
"""Truncate the fires table before and after a DB-backed test."""
import asyncio
async def _truncate():
conn = await asyncpg.connect(
host=os.environ["DB_HOST"],
port=int(os.environ["DB_PORT"]),
user=os.environ["DB_USER"],
password=os.environ["DB_PASSWORD"],
database=os.environ["DB_NAME"],
)
try:
await conn.execute("TRUNCATE fires")
finally:
await conn.close()
asyncio.run(_truncate())
yield
asyncio.run(_truncate())
def make_fire_msg(**overrides) -> dict:
"""A realistic VIIRS hotspot NATS message (as fire_sources publishes it)."""
msg = {
"source_type": "fire",
"latitude": 39.45678,
"longitude": -121.12345,
"brightness": 341.40,
"confidence": "h",
"acq_time": "2026-08-24T18:10:00+00:00",
"satellite": "N",
"instrument": "VIIRS",
"bright_ti5": 310.20,
"frp": 12.4,
"daynight": "D",
"scan": 0.45,
"track": 0.47,
"version": "2.0NRT",
"raw": None,
}
msg.update(overrides)
return msg