| """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 |
| |
| 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() |