* release: 0.53.0 — Tier B trackers + admin UI bugfix Closes #259 (init resume sentinel), #260 (startup parquet-lock sweep), #261 (materialized schema uses local parquet, not BQ), #265 (admin tables apostrophe → HTML-entity escape). Tracker notes: #262 closed as obsolete (pre-empted by 0.51.0 changes), #266 left open pending UX clarification. * fix(init): move resume sentinel from .agnes/ to .claude/ The clean-install integration test (test_clean_install_integration.py) forbids creating .agnes/ in the workspace root via its forbidden_unconditional list — that path is reserved for ~/.agnes/ in the user's HOME (marketplace clone, CA bundle). .claude/ is already created by agnes init for settings.json + hooks, so dropping init-complete next to those keeps the resume sentinel consistent with the rest of Claude Code's workspace surface and lets the clean-install assertions pass. Issue #259. * docs(changelog): point #259 entry at new .claude/init-complete path Follows the sentinel move from .agnes/ → .claude/ to keep the changelog in sync with what 0.53.0 actually ships.
64 lines
2.4 KiB
Python
64 lines
2.4 KiB
Python
"""Materialized BQ tables read schema from the local parquet, not from
|
|
BigQuery INFORMATION_SCHEMA. Issue #261 fix verification."""
|
|
|
|
from pathlib import Path
|
|
from unittest.mock import patch
|
|
|
|
|
|
def test_materialized_bq_schema_does_not_call_bq(tmp_path: Path, monkeypatch):
|
|
"""When `query_mode='materialized'`, `build_schema_uncached` must
|
|
bypass `_fetch_bq_schema` and read from the local parquet."""
|
|
monkeypatch.setenv("DATA_DIR", str(tmp_path))
|
|
# Create a tiny parquet with two columns so the parquet reader has
|
|
# something to DESCRIBE.
|
|
import duckdb
|
|
parquet_dir = tmp_path / "extracts" / "bigquery" / "data"
|
|
parquet_dir.mkdir(parents=True)
|
|
parquet_path = parquet_dir / "orders.parquet"
|
|
conn = duckdb.connect(":memory:")
|
|
conn.execute(
|
|
f"COPY (SELECT 1 AS event_id, 'USD' AS currency) TO '{parquet_path}' (FORMAT PARQUET)"
|
|
)
|
|
conn.close()
|
|
|
|
from app.api.v2_schema import build_schema_uncached
|
|
fake_row = {
|
|
"id": "orders",
|
|
"source_type": "bigquery",
|
|
"query_mode": "materialized",
|
|
"bucket": "dwh",
|
|
"source_table": "orders",
|
|
}
|
|
fake_bq = object() # never used — BQ path must be skipped
|
|
|
|
with patch("app.api.v2_schema._fetch_bq_schema") as mock_bq_schema, \
|
|
patch("app.api.v2_schema._fetch_bq_table_options") as mock_bq_opts:
|
|
result = build_schema_uncached(
|
|
conn=None, table_id="orders", bq=fake_bq, row=fake_row,
|
|
)
|
|
mock_bq_schema.assert_not_called()
|
|
mock_bq_opts.assert_not_called()
|
|
cols = {c["name"] for c in result["columns"]}
|
|
assert cols == {"event_id", "currency"}
|
|
assert result["sql_flavor"] == "duckdb"
|
|
|
|
|
|
def test_remote_bq_schema_still_calls_bq(tmp_path: Path, monkeypatch):
|
|
"""Sanity: remote BQ tables (not materialized) still go through BQ."""
|
|
monkeypatch.setenv("DATA_DIR", str(tmp_path))
|
|
from app.api.v2_schema import build_schema_uncached
|
|
fake_row = {
|
|
"id": "ue",
|
|
"source_type": "bigquery",
|
|
"query_mode": "remote",
|
|
"bucket": "finance",
|
|
"source_table": "ue",
|
|
}
|
|
fake_bq = object()
|
|
with patch(
|
|
"app.api.v2_schema._fetch_bq_schema", return_value=[],
|
|
) as mock_bq_schema, patch(
|
|
"app.api.v2_schema._fetch_bq_table_options", return_value={},
|
|
):
|
|
build_schema_uncached(conn=None, table_id="ue", bq=fake_bq, row=fake_row)
|
|
mock_bq_schema.assert_called_once()
|