Cuts release 0.20.0. ## Highlights - X-Request-ID header on every response + sanitized to [A-Za-z0-9_-] (CRLF log-forging mitigation) - Error pages (HTML + JSON 500) surface request_id for support tickets - Dev debug toolbar gated by DEBUG=1 — fastapi-debug-toolbar with custom DuckDBPanel - Centralized app.logging_config.setup_logging() replaces 23 scattered basicConfig calls - Telegram bot drops bot.log file — stdout only (BREAKING) ## Devin findings addressed - BUG_0001: .env.template no longer claims FastAPI debug=True - BUG_0002: subprocess extractor logs INFO to stderr again - ANALYSIS_0003: _wants_html no longer matches Accept: */* (curl gets JSON as before) - BUG on b1c6ee9: HTML 500 page no longer leaks str(exc) in production - BUG on b13d2fe: 2 CLAUDE.md compliance flags (transform.py + ws_gateway) accepted as scope-limited logging refactor — follow-up to update CLAUDE.md if needed See CHANGELOG [0.20.0] for full notes.
94 lines
2.8 KiB
Python
94 lines
2.8 KiB
Python
import duckdb
|
|
import pytest
|
|
|
|
from app.debug.duckdb_panel import (
|
|
InstrumentedConnection,
|
|
_request_store,
|
|
get_request_store,
|
|
record_query,
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def store_token():
|
|
"""Provide a fresh request-scoped store."""
|
|
token = _request_store.set([])
|
|
yield
|
|
_request_store.reset(token)
|
|
|
|
|
|
@pytest.fixture
|
|
def conn():
|
|
return duckdb.connect(":memory:")
|
|
|
|
|
|
def test_records_query(store_token, conn):
|
|
inst = InstrumentedConnection(conn, "system")
|
|
inst.execute("SELECT 1")
|
|
store = get_request_store()
|
|
assert len(store) == 1
|
|
q = store[0]
|
|
assert q.db == "system"
|
|
assert q.sql == "SELECT 1"
|
|
assert q.error is None
|
|
assert q.ms >= 0
|
|
|
|
|
|
def test_records_query_with_params(store_token, conn):
|
|
inst = InstrumentedConnection(conn, "analytics")
|
|
inst.execute("SELECT $1::INT", [42])
|
|
store = get_request_store()
|
|
assert len(store) == 1
|
|
assert store[0].params == [42]
|
|
|
|
|
|
def test_records_error(store_token, conn):
|
|
inst = InstrumentedConnection(conn, "system")
|
|
with pytest.raises(duckdb.Error):
|
|
inst.execute("SELECT * FROM bogus_table_xyz")
|
|
store = get_request_store()
|
|
assert len(store) == 1
|
|
assert store[0].error is not None
|
|
assert "bogus_table_xyz" in store[0].error or "does not exist" in store[0].error.lower()
|
|
|
|
|
|
def test_db_tag_preserved(store_token):
|
|
a = duckdb.connect(":memory:")
|
|
b = duckdb.connect(":memory:")
|
|
InstrumentedConnection(a, "system").execute("SELECT 1")
|
|
InstrumentedConnection(b, "analytics").execute("SELECT 2")
|
|
store = get_request_store()
|
|
assert {q.db for q in store} == {"system", "analytics"}
|
|
|
|
|
|
def test_no_op_outside_request(conn):
|
|
"""When _request_store is None (outside a debug request), do not raise."""
|
|
assert get_request_store() is None
|
|
inst = InstrumentedConnection(conn, "system")
|
|
inst.execute("SELECT 1") # must not raise
|
|
assert get_request_store() is None
|
|
|
|
|
|
def test_passthrough_attributes(conn):
|
|
"""Wrapper must delegate non-execute methods to the real connection."""
|
|
inst = InstrumentedConnection(conn, "system")
|
|
inst.execute("CREATE TABLE t (x INT)")
|
|
inst.execute("INSERT INTO t VALUES (1), (2), (3)")
|
|
rows = inst.execute("SELECT x FROM t ORDER BY x").fetchall()
|
|
assert rows == [(1,), (2,), (3,)]
|
|
|
|
|
|
def test_cursor_returns_instrumented(store_token, conn):
|
|
"""A cursor() call returns an InstrumentedConnection wrapping the real cursor."""
|
|
inst = InstrumentedConnection(conn, "system")
|
|
cur = inst.cursor()
|
|
assert isinstance(cur, InstrumentedConnection)
|
|
cur.execute("SELECT 99")
|
|
store = get_request_store()
|
|
assert len(store) == 1
|
|
assert store[0].db == "system"
|
|
|
|
|
|
def test_record_query_no_op_when_store_none():
|
|
"""record_query is safe to call when no store is set."""
|
|
record_query("system", "SELECT 1", None, 0.0, None) # must not raise
|