datasette/tests/test_tracer.py
Simon Willison 7f93353549
Fix startup hook to fire after metadata and schema tables are populated (#2666)
* Fix startup hook to fire after metadata and schema tables are populated

Previously, the startup() plugin hook fired before internal database
tables were populated from metadata.yaml and before catalog schema
tables were filled. This meant plugins couldn't read or modify metadata
during startup. Now invoke_startup() calls refresh_schemas() before
firing startup hooks, ensuring metadata and catalog tables are available.

* Fix startup hook to fire after metadata and schema tables are populated

Previously, the startup() plugin hook fired before internal database
tables were populated from metadata.yaml and before catalog schema
tables were filled. This meant plugins couldn't read or modify metadata
during startup. Now invoke_startup() calls _refresh_schemas() before
firing startup hooks, ensuring metadata and catalog tables are available.

Updated test_tracer to reflect that internal DB creation SQL now runs
during startup rather than during the first traced request.

* Move check_databases before invoke_startup in CLI serve

Since invoke_startup now calls _refresh_schemas() which queries each
database, the spatialite connection check must run first to provide
the friendly error message instead of a raw OperationalError.

https://claude.ai/code/session_01KL4t5FZYb32rZY7xaqrrZU
2026-03-16 17:56:40 -07:00

85 lines
3.3 KiB
Python

import pytest
from .fixtures import make_app_client
@pytest.mark.parametrize("trace_debug", (True, False))
def test_trace(trace_debug):
with make_app_client(settings={"trace_debug": trace_debug}) as client:
response = client.get("/fixtures/simple_primary_key.json?_trace=1")
assert response.status == 200
data = response.json
if not trace_debug:
assert "_trace" not in data
return
assert "_trace" in data
trace_info = data["_trace"]
assert isinstance(trace_info["request_duration_ms"], float)
assert isinstance(trace_info["sum_trace_duration_ms"], float)
assert isinstance(trace_info["num_traces"], int)
assert isinstance(trace_info["traces"], list)
traces = trace_info["traces"]
assert len(traces) == trace_info["num_traces"]
for trace in traces:
assert isinstance(trace["type"], str)
assert isinstance(trace["start"], float)
assert isinstance(trace["end"], float)
assert trace["duration_ms"] == (trace["end"] - trace["start"]) * 1000
assert isinstance(trace["traceback"], list)
assert isinstance(trace["database"], str)
assert isinstance(trace["sql"], str)
assert isinstance(trace.get("params"), (list, dict, None.__class__))
sqls = [trace["sql"] for trace in traces if "sql" in trace]
# There should be SQL statements from request handling in the trace.
# Note: CREATE TABLE, INSERT OR REPLACE, executescript, and executemany
# are not expected here because internal tables are now created and
# populated during invoke_startup(), before the request is traced.
assert any(sql.startswith("select ") for sql in sqls), "No select statements traced"
def test_trace_silently_fails_for_large_page():
# Max HTML size is 256KB
with make_app_client(settings={"trace_debug": True}) as client:
# Small response should have trace
small_response = client.get("/fixtures/simple_primary_key.json?_trace=1")
assert small_response.status == 200
assert "_trace" in small_response.json
# Big response should not
big_response = client.get(
"/fixtures/-/query.json",
params={"_trace": 1, "sql": "select zeroblob(1024 * 256)"},
)
assert big_response.status == 200
assert "_trace" not in big_response.json
def test_trace_query_errors():
with make_app_client(settings={"trace_debug": True}) as client:
response = client.get(
"/fixtures/-/query.json",
params={"_trace": 1, "sql": "select * from non_existent_table"},
)
assert response.status == 400
data = response.json
assert "_trace" in data
trace_info = data["_trace"]
assert trace_info["traces"][-1]["error"] == "no such table: non_existent_table"
def test_trace_parallel_queries():
with make_app_client(settings={"trace_debug": True}) as client:
response = client.get("/parallel-queries?_trace=1")
assert response.status == 200
data = response.json
assert data["one"] == 1
assert data["two"] == 2
trace_info = data["_trace"]
traces = [trace for trace in trace_info["traces"] if "sql" in trace]
one, two = traces
# "two" should have started before "one" ended
assert two["start"] < one["end"]