agnes-the-ai-analyst/tests/test_cli_snapshot_create.py
ZdenekSrotyr 3d58768143 fix: address Devin Review findings — incomplete renames + estimate guard
13 Devin findings across 10 files:

🔴 Critical:
- app/api/v2_catalog.py:42 — `_fetch_hint` returns `da fetch` in /api/v2/catalog
  responses (user-visible in every catalog list)
- cli/skills/agnes-data-querying.md — 11 stale `da fetch`/`da sync` refs in the
  bundled skill markdown
- config/claude_md_template.txt:38 — referenced `agnes pull --docs-only` flag
  that does NOT exist in agnes pull (removed; spec only ships --quiet/--json/
  --dry-run)

🟡 Important:
- app/api/admin.py:252 — `da fetch` in bq_max_scan_bytes hint
- cli/commands/auth.py:119 — `da sync` in import-token docstring (--help text)
- cli/commands/tokens.py:48 — "Export it so `da` can use it" prose
- ARCHITECTURE.md — 4 stale rows in CLI commands table
- README.md — stale paragraphs for analysts (da sync, da analyst setup)

🚩 Substantive observations addressed:
- app/api/query.py:249,302,489 — server-side error/help strings still said
  `da sync`/`da fetch` (returned in API responses to clients)
- cli/commands/snapshot.py:235-241 — DuckDB existence guard incorrectly
  blocked `--estimate` (server-side dry-run that never opens local DB).
  Added test ensuring estimate path skips the guard.

Skipped (intentionally historical):
- app/api/admin.py:2377,2429,2437 — historical comments describing past
  manifest-vs-sync_state bug; past tense, accurate to keep as `da sync`.
2026-05-04 20:05:06 +02:00

63 lines
2.4 KiB
Python

"""Tests for `agnes snapshot create` (folded from `da fetch`)."""
from typer.testing import CliRunner
# CI-safety: Typer/rich emits ANSI escapes in --help output. Strip before asserts.
_ANSI_RE = __import__("re").compile(r"\x1b\[[0-9;]*m")
def _clean(s: str) -> str:
return _ANSI_RE.sub("", s)
from cli.commands.snapshot import snapshot_app
def test_snapshot_create_help():
runner = CliRunner()
result = runner.invoke(snapshot_app, ["create", "--help"])
assert result.exit_code == 0
for flag in [
"--select",
"--where",
"--limit",
"--order-by",
"--as",
"--estimate",
"--no-estimate",
"--force",
]:
assert flag in _clean(result.output)
def test_snapshot_create_no_duckdb_friendly_exit(tmp_path, monkeypatch):
"""Real-fetch path (no --estimate) refuses without a local DuckDB."""
monkeypatch.setenv("AGNES_LOCAL_DIR", str(tmp_path))
runner = CliRunner()
result = runner.invoke(snapshot_app, ["create", "any_table", "--as", "x"])
assert result.exit_code == 1
out = result.output + (result.stderr or "")
assert "Run: agnes pull" in out
def test_snapshot_create_estimate_skips_duckdb_guard(tmp_path, monkeypatch):
"""--estimate is server-side dry-run only; doesn't need local DuckDB.
Analysts use it pre-bootstrap to scope a fetch before committing to
materialize, so the local-DB guard would block the use case it's most
useful for. Per Devin review finding ANALYSIS_0004.
"""
monkeypatch.setenv("AGNES_LOCAL_DIR", str(tmp_path))
# Stub api_post so we don't actually hit the network — what we care about
# is that the guard doesn't fire BEFORE the API call.
from unittest.mock import MagicMock
fake_resp = MagicMock()
fake_resp.status_code = 200
fake_resp.json.return_value = {"estimated_scan_bytes": 0, "estimated_rows": 0,
"estimated_local_bytes": 0, "table_id": "any_table"}
monkeypatch.setattr("cli.commands.snapshot.api_post", lambda *a, **kw: fake_resp,
raising=False)
runner = CliRunner()
result = runner.invoke(snapshot_app, ["create", "any_table", "--as", "x", "--estimate"])
# Should NOT exit 1 with "Run: agnes pull" — that hint is for the fetch path.
out = result.output + (result.stderr or "")
assert "Run: agnes pull" not in out, \
"--estimate must not be blocked by the local-DuckDB guard"