"""Integration test fixtures using real DBs in containers. Per v4.0 §T21. Mocks prove the code compiles — real DBs prove it works. """ import pytest import pytest_asyncio @pytest.fixture(scope="session") def postgres_container(): from testcontainers.postgres import PostgresContainer pg = PostgresContainer("postgres:16-alpine") pg.start() yield pg pg.stop() @pytest_asyncio.fixture async def pg_conn(postgres_container): import asyncpg url = postgres_container.get_connection_url() if url.startswith("postgresql+psycopg2://"): url = "postgresql://" + url[len("postgresql+psycopg2://"):] conn = await asyncpg.connect(url) await _init_pg_schema(conn) try: yield conn finally: await conn.close() async def _init_pg_schema(conn) -> None: await conn.execute(""" CREATE TABLE IF NOT EXISTS tokens ( token_id TEXT PRIMARY KEY, chain TEXT NOT NULL, address TEXT NOT NULL, symbol TEXT, name TEXT, decimals INT DEFAULT 18, deployer_wallet_id TEXT, deployed_at TIMESTAMPTZ DEFAULT NOW(), initial_supply BIGINT, current_supply BIGINT, is_honeypot BOOLEAN, is_mintable BOOLEAN, is_proxy BOOLEAN, tax_buy_bps INT, tax_sell_bps INT, risk_tier TEXT, risk_score INT, risk_factors JSONB DEFAULT '[]', rag_embedding_id TEXT, UNIQUE(chain, address) ); CREATE TABLE IF NOT EXISTS wallets ( wallet_id TEXT PRIMARY KEY, chain TEXT NOT NULL, address TEXT NOT NULL, first_seen TIMESTAMPTZ DEFAULT NOW(), last_seen TIMESTAMPTZ DEFAULT NOW(), tx_count INT DEFAULT 0, total_volume_usd DOUBLE PRECISION DEFAULT 0.0, is_deployer BOOLEAN DEFAULT FALSE, reputation_score INT ); CREATE TABLE IF NOT EXISTS news_items ( news_id TEXT PRIMARY KEY, url TEXT NOT NULL, title TEXT NOT NULL, summary TEXT, published_at TIMESTAMPTZ DEFAULT NOW(), ingested_at TIMESTAMPTZ DEFAULT NOW(), source TEXT, sentiment_score DOUBLE PRECISION, body_markdown TEXT, chains_mentioned JSONB DEFAULT '[]', tokens_mentioned JSONB DEFAULT '[]', wallets_mentioned JSONB DEFAULT '[]', ai_analysis TEXT ); CREATE TABLE IF NOT EXISTS scan_reports ( report_id TEXT PRIMARY KEY, subject_type TEXT NOT NULL, subject_id TEXT NOT NULL, generated_at TIMESTAMPTZ DEFAULT NOW(), generated_by_model TEXT, risk_score INT, risk_tier TEXT, markdown_url TEXT ); """) @pytest.fixture(scope="session") def redis_container(): from testcontainers.redis import RedisContainer r = RedisContainer("redis:7-alpine") r.start() yield r r.stop() @pytest_asyncio.fixture async def redis_client(redis_container): import redis.asyncio as aioredis try: url = redis_container.get_connection_url() except AttributeError: host = redis_container.get_container_host_ip() port = redis_container.get_exposed_port(6379) url = f"redis://{host}:{port}" client = aioredis.from_url(url) try: yield client finally: try: await client.flushdb() except Exception: pass await client.aclose() @pytest.fixture(scope="session") def neo4j_container(): from testcontainers.neo4j import Neo4jContainer n = Neo4jContainer("neo4j:5") n.start() yield n n.stop() @pytest_asyncio.fixture async def neo4j_driver(neo4j_container): from neo4j import AsyncGraphDatabase # testcontainers Neo4jContainer exposes `.password` attribute password = getattr(neo4j_container, "password", "admin") uri = neo4j_container.get_connection_url() driver = AsyncGraphDatabase.driver(uri, auth=("neo4j", password)) try: yield driver finally: await driver.close()