agnes-the-ai-analyst/tests/test_cli_admin_materialized.py
ZdenekSrotyr 1563b05f2e refactor(cli): hard-cutover env vars + config dir to AGNES_*
Task 0.5 of clean-analyst-bootstrap. Greenfield rewrite — no fallback,
no aliases. Existing dev environments lose their cached PAT and must
re-authenticate.

Env var renames (hard cutover):
- DA_CONFIG_DIR    -> AGNES_CONFIG_DIR
- DA_SERVER        -> AGNES_SERVER
- DA_SERVER_URL    -> AGNES_SERVER_URL  (test-only stale ref, not in spec)
- DA_NO_UPDATE_CHECK -> AGNES_NO_UPDATE_CHECK
- DA_LOCAL_DIR     -> AGNES_LOCAL_DIR
- DA_TOKEN         -> AGNES_TOKEN
- DA_STREAM_RETRIES -> AGNES_STREAM_RETRIES

Config dir rename: ~/.config/da/ -> ~/.config/agnes/ (across code,
comments, docstrings, error messages, install templates, dev scripts).

Stale `da X` references in CLI source (and adjacent app/, tests/):
swept docstrings, comments, help text, and error messages where the
verb survives the rewrite (init, pull, push, catalog, status, diagnose,
auth, admin, skills, query, schema, describe, explore, disk-info,
snapshot, login, logout, whoami, server, setup) and replaced `da X`
with `agnes X`. Intentionally kept `da sync`, `da fetch`, `da analyst`,
`da metrics` — those verbs are removed in later tasks; the legacy
strings will be detected by `_LEGACY_STRINGS` (added in Task 2).

Test fixes:
- TestCLIVersion now asserts output starts with `agnes ` (was `da `).

Test results: 2675 passed, 25 skipped (full pytest run, excluding 9
pre-existing test_db.py / test_user_management.py / test_e2e_extract.py
/ test_cli_binary_rename.py failures unrelated to this rename).
2026-05-04 16:35:44 +02:00

157 lines
4.8 KiB
Python

"""`agnes admin register-table --query-mode materialized --query @file.sql`
sends source_query in the payload; existing local/remote paths still work
unchanged."""
from typer.testing import CliRunner
from unittest.mock import MagicMock
from cli.main import app
def _fake_resp(status_code, body=None):
resp = MagicMock()
resp.status_code = status_code
resp.json = lambda: body or {"id": "x", "name": "x", "status": "registered"}
return resp
def test_register_materialized_with_inline_query(monkeypatch):
captured = {}
def fake_post(path, json):
captured["path"] = path
captured["json"] = json
return _fake_resp(201)
monkeypatch.setattr("cli.commands.admin.api_post", fake_post)
runner = CliRunner()
result = runner.invoke(app, [
"admin", "register-table", "orders_90d",
"--source-type", "bigquery",
"--query-mode", "materialized",
"--query", "SELECT date FROM `prj.ds.orders`",
"--sync-schedule", "every 6h",
])
assert result.exit_code == 0, result.stdout
assert captured["path"] == "/api/admin/register-table"
assert captured["json"]["query_mode"] == "materialized"
assert captured["json"]["source_query"] == "SELECT date FROM `prj.ds.orders`"
assert captured["json"]["sync_schedule"] == "every 6h"
def test_register_materialized_reads_query_from_file(tmp_path, monkeypatch):
sql_file = tmp_path / "orders.sql"
sql_file.write_text(
"SELECT date, SUM(revenue) FROM `prj.ds.orders` GROUP BY 1\n"
)
captured = {}
def fake_post(path, json):
captured["json"] = json
return _fake_resp(201)
monkeypatch.setattr("cli.commands.admin.api_post", fake_post)
runner = CliRunner()
result = runner.invoke(app, [
"admin", "register-table", "orders_90d",
"--source-type", "bigquery",
"--query-mode", "materialized",
"--query", f"@{sql_file}",
"--sync-schedule", "daily 03:00",
])
assert result.exit_code == 0, result.stdout
assert "SELECT date, SUM(revenue)" in captured["json"]["source_query"]
assert not captured["json"]["source_query"].endswith("\n")
def test_register_materialized_without_query_fails(monkeypatch):
"""--query-mode materialized without --query is a client-side error,
no API call made."""
called = {"count": 0}
def fake_post(*args, **kwargs):
called["count"] += 1
return _fake_resp(201)
monkeypatch.setattr("cli.commands.admin.api_post", fake_post)
runner = CliRunner()
result = runner.invoke(app, [
"admin", "register-table", "orders_90d",
"--source-type", "bigquery",
"--query-mode", "materialized",
])
assert result.exit_code != 0
assert called["count"] == 0
combined = result.stdout + (result.stderr or "")
assert "--query" in combined
def test_register_local_mode_does_not_send_source_query(monkeypatch):
"""Default local mode shouldn't send source_query — server-side
validator forbids it on local."""
captured = {}
def fake_post(path, json):
captured["json"] = json
return _fake_resp(201)
monkeypatch.setattr("cli.commands.admin.api_post", fake_post)
runner = CliRunner()
result = runner.invoke(app, [
"admin", "register-table", "kbc_orders",
"--source-type", "keboola",
"--bucket", "in.c-crm",
])
assert result.exit_code == 0
assert "source_query" not in captured["json"]
assert "sync_schedule" not in captured["json"]
def test_register_query_at_path_missing_file_fails(monkeypatch):
"""@file.sql where the file doesn't exist surfaces a clear error."""
monkeypatch.setattr(
"cli.commands.admin.api_post", lambda *a, **kw: _fake_resp(201),
)
runner = CliRunner()
result = runner.invoke(app, [
"admin", "register-table", "x",
"--source-type", "bigquery",
"--query-mode", "materialized",
"--query", "@/tmp/definitely-does-not-exist-9b4f7e2c.sql",
])
assert result.exit_code != 0
def test_register_remote_path_unchanged(monkeypatch):
"""The pre-existing --bucket / --source-table / --query-mode remote
flow still works without --query."""
captured = {}
def fake_post(path, json):
captured["json"] = json
return _fake_resp(200)
monkeypatch.setattr("cli.commands.admin.api_post", fake_post)
runner = CliRunner()
result = runner.invoke(app, [
"admin", "register-table", "live_orders",
"--source-type", "bigquery",
"--bucket", "analytics",
"--source-table", "orders",
"--query-mode", "remote",
])
assert result.exit_code == 0
assert captured["json"]["query_mode"] == "remote"
assert "source_query" not in captured["json"]
assert captured["json"]["bucket"] == "analytics"
assert captured["json"]["source_table"] == "orders"