DataDesigner/packages/data-designer/tests/cli/commands/test_agent_command.py
Johnny Greco 4c19dba74b
feat: agent CLI introspection (simplified) (#415)
* feat: add agent introspection cli

* refactor: remove agent cli schema version

* refactor: omit missing builder docstrings from context

* refactor: tighten agent cli contract

* feat: add schema_text() to ConfigBase for human-readable field summaries

ConfigBase.schema_text() returns a concise text representation including
the class docstring summary, field names, types, defaults, and
descriptions. Field descriptions added to column config types to
surface through this method.

* refactor: flatten agent CLI into plain functions with text output mode

Delete AgentController class and agent_command_defs module. Move all
logic into agent_introspection (data) and agent_text_formatter (display)
as plain functions. Add --json flag so commands default to human-readable
text using schema_text(), with JSON as opt-in. Unify _emit helper,
remove include_docstrings parameter, deduplicate catalog calls, and fix
N+1 discover_family_types in get_family_schemas.

* fix: port stale controller tests and consolidate command descriptions

Port test_agent_controller.py to use plain functions instead of deleted
AgentController. Extract AGENT_COMMANDS constant as single source for
operation descriptions, syncing with main.py help strings.

* style: fix ruff formatting in agent_introspection

* refactor: centralize agent command definitions

Extract AGENT_COMMANDS into agent_command_defs.py so main.py and
agent_introspection.py share a single source for command names,
help text, and metadata. The new module has no heavy dependencies,
keeping --help latency unaffected.

* fix: handle default_factory and empty providers in schema_text and introspection

- schema_text() now detects default_factory fields and renders e.g. "list()"
  instead of leaking PydanticUndefined
- Guard against IndexError when provider registry has an empty providers list
- Add 15 edge-case tests for schema_text covering default_factory, enum
  defaults, None defaults, scalar defaults, descriptions, and docstrings

* refactor: remove JSON output mode from agent CLI commands

Text-only output simplifies the interface. Structured output can be
added back trivially since the functions already return dicts.

* docs: update schema_text docstring to reflect agent focus

* fix: include builder section and import_path in agent text output

- format_context_text now renders a ## Builder section
- format_types_text now includes import_path column in tables

* refactor: drop import_path from types tables

All config objects are imported via dd.<ClassName>, so the full import
path is redundant noise in agent output.

* docs: add family definition and import hint to context output

* refactor: rename Types section to Families, drop redundant "types" from sub-headers

* fix: coerce None to empty string in table cells

row.get(col, '') returns None when the key exists with value None,
causing str(None) to render "None" in the output. Use `or ''` instead.

* refactor: move agent controller tests to utils as introspection integration tests

There is no controller layer — these tests exercise functions in
agent_introspection.py, so they belong in tests/cli/utils/.

* fix: only coerce None to empty string in table cells, not False

The previous `or ''` pattern treated all falsy values (including False)
as empty. Use an explicit None check so booleans render correctly.

* style: address review nits from nabin

- Add explicit parentheses to and/or precedence in _build_agent_lazy_group
- Rename loop variable l to line in test_schema_text
- Move get_family_schema import to module level in test_agent_text_formatter

* fix: improve schema_text Literal display, builder signature quotes, and docstring parsing

- _format_annotation now renders Literal['value'] instead of bare Literal
- _format_signature strips quotes from stringified annotations caused by
  `from __future__ import annotations`
- _get_docstring_summary stops at any Google-style section header, not
  just Attributes:
2026-03-13 18:26:00 -04:00

65 lines
2.3 KiB
Python

# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
from __future__ import annotations
from unittest.mock import patch
import pytest
from typer.testing import CliRunner
from data_designer.cli.main import app
_PATCH = "data_designer.cli.commands.agent"
@pytest.mark.parametrize(
"args,data_fn,format_fn,expected_text",
[
(["agent", "context"], "get_context", "format_context_text", "Data Designer"),
(["agent", "types", "columns"], "get_types", "format_types_text", "columns"),
(["agent", "builder"], "get_builder_api", "format_builder_text", "Builder:"),
(["agent", "state", "model-aliases"], "get_model_aliases_state", "format_model_aliases_text", "model aliases"),
(
["agent", "state", "persona-datasets"],
"get_persona_datasets_state",
"format_persona_datasets_text",
"persona",
),
],
ids=["context", "types", "builder", "model-aliases", "persona-datasets"],
)
def test_commands_default_text_mode(args: list[str], data_fn: str, format_fn: str, expected_text: str) -> None:
runner = CliRunner()
with (
patch(f"{_PATCH}.{data_fn}", return_value={"stub": True}) as mock_get,
patch(f"{_PATCH}.{format_fn}", return_value=expected_text),
):
result = runner.invoke(app, args)
assert result.exit_code == 0
assert expected_text in result.output
mock_get.assert_called_once()
def test_schema_command_default_outputs_text() -> None:
runner = CliRunner()
with (
patch(f"{_PATCH}.get_schema", return_value={"type_name": "llm-text", "schema": {}}),
patch(f"{_PATCH}.format_schema_text", return_value="# llm-text\n{}"),
):
result = runner.invoke(app, ["agent", "schema", "columns", "llm-text"])
assert result.exit_code == 0
assert "llm-text" in result.output
def test_error_outputs_message_to_stderr() -> None:
runner = CliRunner()
with patch(f"{_PATCH}.get_schema", side_effect=ValueError("boom")):
result = runner.invoke(app, ["agent", "schema", "columns", "missing"])
assert result.exit_code == 1
assert result.stdout == ""
assert "internal_error" in result.stderr
assert "boom" in result.stderr