DataDesigner/packages/data-designer/tests/cli/commands/test_create_command.py
Johnny Greco d3c4de76da
feat: add preview, create, and validate CLI commands (#313)
* feat: add preview, create, and validate CLI commands

Add three new top-level CLI commands for the data-designer workflow:
- `data-designer preview` - generate preview datasets for fast iteration
- `data-designer create` - create full datasets and save to disk
- `data-designer validate` - validate configuration files

Also includes:
- Move wait_for_navigation_key() UI primitive from preview.py to ui.py
- Add KeyPressEvent type annotations to all key binding handlers in ui.py
- Refactor cli/utils.py into cli/utils/ package with config_loader module
- Comprehensive test coverage for all new commands

* fix: update pythonjsonlogger import and clean up dev dependencies

- Update pythonjsonlogger import to use newer JsonFormatter API
- Consolidate dev-dependencies into [dependency-groups] dev section
- Remove unnecessary test cli/utils __init__.py

* small E

* address greptile feedback

* organize CLI commands into rich help panels

Group top-level commands under "Generation" and "Setup" panels
for clearer help output.

* refactor config loader to parse files directly and auto-detect config format

- Parse YAML/JSON files into dicts before passing to from_config,
  providing format-specific error messages for parse failures
- Auto-detect DataDesignerConfig format (columns at top level) and
  wrap it into BuilderConfig so users can provide either format
- Clean up Python module loading with try/except/finally for reliable
  sys.modules and sys.path cleanup
- Add comprehensive tests for parsing, validation, and auto-wrapping

* fix sys.path cleanup in config loader and simplify tests

- Use pop(0) instead of remove() to precisely undo the insert(0, ...)
  and avoid accidentally removing a different matching path entry
- Replace MagicMock with real DataDesignerConfigBuilder in tests

* move config format auto-detection into from_config

Centralize the shorthand DataDesignerConfig detection (columns at
top level without a data_designer wrapper) in
DataDesignerConfigBuilder.from_config so all callers benefit, not
just the CLI config loader. Simplify config_loader to delegate file
parsing and format normalization entirely to from_config.

* extract GenerationController from CLI commands

Move shared generation logic (preview, validate, create) out of the
individual Typer command functions into a dedicated GenerationController,
matching the existing controller pattern (DownloadController, etc.).
The command functions now delegate to the controller, keeping them as
thin entry points. Tests updated accordingly — command tests verify
delegation while controller tests cover the full behavior.

* harden sys.path cleanup and add explanatory comments

Use sys.path.remove() instead of checking sys.path[0] so cleanup
succeeds even when exec_module inserts entries at index 0. Drop
unnecessary spec=DataDesignerConfigBuilder from test mocks.

* check stdout TTY in preview interactive mode detection

Previously only stdin was checked, so piping stdout (e.g.
`dd preview cfg.yaml | head`) would still attempt interactive
browsing. Now both stdin and stdout must be a TTY.
2026-02-11 14:06:06 -05:00

66 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 MagicMock, patch
from data_designer.cli.commands.create import create_command
# ---------------------------------------------------------------------------
# create_command delegation tests
# ---------------------------------------------------------------------------
@patch("data_designer.cli.commands.create.GenerationController")
def test_create_command_delegates_to_controller(mock_ctrl_cls: MagicMock) -> None:
"""Test create_command delegates to GenerationController.run_create."""
mock_ctrl = MagicMock()
mock_ctrl_cls.return_value = mock_ctrl
create_command(config_source="config.yaml", num_records=10, dataset_name="dataset", artifact_path=None)
mock_ctrl_cls.assert_called_once()
mock_ctrl.run_create.assert_called_once_with(
config_source="config.yaml",
num_records=10,
dataset_name="dataset",
artifact_path=None,
)
@patch("data_designer.cli.commands.create.GenerationController")
def test_create_command_passes_custom_options(mock_ctrl_cls: MagicMock) -> None:
"""Test create_command passes custom options to the controller."""
mock_ctrl = MagicMock()
mock_ctrl_cls.return_value = mock_ctrl
create_command(
config_source="config.py",
num_records=100,
dataset_name="my_data",
artifact_path="/custom/output",
)
mock_ctrl.run_create.assert_called_once_with(
config_source="config.py",
num_records=100,
dataset_name="my_data",
artifact_path="/custom/output",
)
@patch("data_designer.cli.commands.create.GenerationController")
def test_create_command_default_artifact_path_is_none(mock_ctrl_cls: MagicMock) -> None:
"""Test create_command passes artifact_path=None when not specified."""
mock_ctrl = MagicMock()
mock_ctrl_cls.return_value = mock_ctrl
create_command(config_source="config.yaml", num_records=5, dataset_name="ds", artifact_path=None)
mock_ctrl.run_create.assert_called_once_with(
config_source="config.yaml",
num_records=5,
dataset_name="ds",
artifact_path=None,
)