Skip to content

Commit 38f2400

Browse files
committed
tests for commands
1 parent d2176e7 commit 38f2400

File tree

3 files changed

+368
-0
lines changed

3 files changed

+368
-0
lines changed

tests/cli/commands/test_models.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
from pathlib import Path
5+
from unittest.mock import MagicMock, patch
6+
7+
from data_designer.cli.commands.models import models_command
8+
from data_designer.cli.constants import DEFAULT_CONFIG_DIR
9+
from data_designer.cli.controllers.model_controller import ModelController
10+
11+
12+
@patch("data_designer.cli.commands.models.ModelController")
13+
def test_models_command(mock_model_controller):
14+
mock_model_controller_instance = MagicMock(spec=ModelController)
15+
mock_model_controller.return_value = mock_model_controller_instance
16+
models_command(config_dir=None)
17+
mock_model_controller.assert_called_once()
18+
mock_model_controller.call_args[0][0] == DEFAULT_CONFIG_DIR
19+
mock_model_controller_instance.run.assert_called_once()
20+
21+
22+
@patch("data_designer.cli.commands.models.ModelController")
23+
def test_models_command_with_config_dir(mock_model_controller, tmp_path: Path):
24+
mock_model_controller_instance = MagicMock(spec=ModelController)
25+
mock_model_controller.return_value = mock_model_controller_instance
26+
models_command(config_dir=str(tmp_path))
27+
mock_model_controller.assert_called_once()
28+
mock_model_controller.call_args[0][0] == str(tmp_path)
29+
mock_model_controller_instance.run.assert_called_once()
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
from pathlib import Path
5+
from unittest.mock import MagicMock, patch
6+
7+
from data_designer.cli.commands.providers import providers_command
8+
from data_designer.cli.constants import DEFAULT_CONFIG_DIR
9+
from data_designer.cli.controllers.provider_controller import ProviderController
10+
11+
12+
@patch("data_designer.cli.commands.providers.ProviderController")
13+
def test_providers_command(mock_provider_controller):
14+
mock_provider_controller_instance = MagicMock(spec=ProviderController)
15+
mock_provider_controller.return_value = mock_provider_controller_instance
16+
providers_command(config_dir=None)
17+
mock_provider_controller.assert_called_once()
18+
mock_provider_controller.call_args[0][0] == DEFAULT_CONFIG_DIR
19+
mock_provider_controller_instance.run.assert_called_once()
20+
21+
22+
@patch("data_designer.cli.commands.providers.ProviderController")
23+
def test_providers_command_with_config_dir(mock_provider_controller, tmp_path: Path):
24+
mock_provider_controller_instance = MagicMock(spec=ProviderController)
25+
mock_provider_controller.return_value = mock_provider_controller_instance
26+
providers_command(config_dir=str(tmp_path))
27+
mock_provider_controller.assert_called_once()
28+
mock_provider_controller.call_args[0][0] == str(tmp_path)
29+
mock_provider_controller_instance.run.assert_called_once()

tests/cli/commands/test_reset.py

Lines changed: 310 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,310 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
from collections.abc import Callable
5+
from pathlib import Path
6+
from unittest.mock import Mock, patch
7+
8+
import pytest
9+
import typer
10+
11+
from data_designer.cli.commands.reset import reset_command
12+
from data_designer.cli.constants import DEFAULT_CONFIG_DIR
13+
14+
# Type alias for the factory function
15+
MockRepositoryFactory = Callable[
16+
[bool, bool, Exception | None, Exception | None],
17+
tuple[Mock, Mock, Mock, Mock],
18+
]
19+
20+
21+
# Fixtures for common test data
22+
@pytest.fixture
23+
def stub_fake_provider_path() -> Path:
24+
"""Fake path for provider config file."""
25+
return Path("/fake/providers.json")
26+
27+
28+
@pytest.fixture
29+
def stub_fake_model_path() -> Path:
30+
"""Fake path for model config file."""
31+
return Path("/fake/models.json")
32+
33+
34+
# Helper functions for mock setup
35+
def setup_mock_repository(
36+
exists: bool = True,
37+
config_file: Path | None = None,
38+
delete_side_effect: Exception | None = None,
39+
) -> Mock:
40+
"""Create a mock repository instance with common configuration.
41+
42+
Args:
43+
exists: Whether the config file exists
44+
config_file: Path to the config file
45+
delete_side_effect: Optional exception to raise on delete()
46+
"""
47+
mock_instance = Mock()
48+
mock_instance.exists.return_value = exists
49+
if config_file:
50+
mock_instance.config_file = config_file
51+
if delete_side_effect:
52+
mock_instance.delete.side_effect = delete_side_effect
53+
return mock_instance
54+
55+
56+
@pytest.fixture
57+
def mock_repositories_factory(stub_fake_provider_path: Path, stub_fake_model_path: Path) -> MockRepositoryFactory:
58+
"""Factory fixture for creating mock repositories with different configurations."""
59+
60+
def _factory(
61+
provider_exists: bool = False,
62+
model_exists: bool = False,
63+
provider_delete_error: Exception | None = None,
64+
model_delete_error: Exception | None = None,
65+
) -> tuple[Mock, Mock, Mock, Mock]:
66+
"""Create mocked repositories and their instances.
67+
68+
Returns:
69+
Tuple of (mock_provider_repo, mock_provider_instance,
70+
mock_model_repo, mock_model_instance)
71+
"""
72+
mock_provider_instance = setup_mock_repository(
73+
exists=provider_exists,
74+
config_file=stub_fake_provider_path if provider_exists else None,
75+
delete_side_effect=provider_delete_error,
76+
)
77+
mock_provider_repo = Mock(return_value=mock_provider_instance)
78+
79+
mock_model_instance = setup_mock_repository(
80+
exists=model_exists,
81+
config_file=stub_fake_model_path if model_exists else None,
82+
delete_side_effect=model_delete_error,
83+
)
84+
mock_model_repo = Mock(return_value=mock_model_instance)
85+
86+
return mock_provider_repo, mock_provider_instance, mock_model_repo, mock_model_instance
87+
88+
return _factory
89+
90+
91+
# Tests
92+
@patch("data_designer.cli.commands.reset.ModelRepository")
93+
@patch("data_designer.cli.commands.reset.ProviderRepository")
94+
@patch("data_designer.cli.commands.reset.confirm_action")
95+
def test_reset_no_config_files_exist(
96+
mock_confirm: Mock,
97+
mock_provider_repo: Mock,
98+
mock_model_repo: Mock,
99+
mock_repositories_factory: MockRepositoryFactory,
100+
) -> None:
101+
"""Test reset when no configuration files exist - should exit early."""
102+
_, mock_provider_instance, _, mock_model_instance = mock_repositories_factory(
103+
provider_exists=False, model_exists=False
104+
)
105+
mock_provider_repo.return_value = mock_provider_instance
106+
mock_model_repo.return_value = mock_model_instance
107+
108+
with pytest.raises(typer.Exit) as exc_info:
109+
reset_command(config_dir=None)
110+
111+
assert exc_info.value.exit_code == 0
112+
mock_confirm.assert_not_called()
113+
mock_provider_instance.delete.assert_not_called()
114+
mock_model_instance.delete.assert_not_called()
115+
116+
117+
@patch("data_designer.cli.commands.reset.ModelRepository")
118+
@patch("data_designer.cli.commands.reset.ProviderRepository")
119+
@patch("data_designer.cli.commands.reset.confirm_action")
120+
def test_reset_both_files_exist_user_confirms_both(
121+
mock_confirm: Mock,
122+
mock_provider_repo: Mock,
123+
mock_model_repo: Mock,
124+
mock_repositories_factory: MockRepositoryFactory,
125+
) -> None:
126+
"""Test reset when both config files exist and user confirms deletion of both."""
127+
_, mock_provider_instance, _, mock_model_instance = mock_repositories_factory(
128+
provider_exists=True, model_exists=True
129+
)
130+
mock_provider_repo.return_value = mock_provider_instance
131+
mock_model_repo.return_value = mock_model_instance
132+
mock_confirm.return_value = True
133+
134+
reset_command(config_dir=None)
135+
136+
assert mock_confirm.call_count == 2
137+
mock_provider_instance.delete.assert_called_once()
138+
mock_model_instance.delete.assert_called_once()
139+
140+
141+
@patch("data_designer.cli.commands.reset.ModelRepository")
142+
@patch("data_designer.cli.commands.reset.ProviderRepository")
143+
@patch("data_designer.cli.commands.reset.confirm_action")
144+
def test_reset_both_files_exist_user_declines_both(
145+
mock_confirm: Mock,
146+
mock_provider_repo: Mock,
147+
mock_model_repo: Mock,
148+
mock_repositories_factory: MockRepositoryFactory,
149+
) -> None:
150+
"""Test reset when both config files exist but user declines deletion."""
151+
_, mock_provider_instance, _, mock_model_instance = mock_repositories_factory(
152+
provider_exists=True, model_exists=True
153+
)
154+
mock_provider_repo.return_value = mock_provider_instance
155+
mock_model_repo.return_value = mock_model_instance
156+
mock_confirm.return_value = False
157+
158+
reset_command(config_dir=None)
159+
160+
assert mock_confirm.call_count == 2
161+
mock_provider_instance.delete.assert_not_called()
162+
mock_model_instance.delete.assert_not_called()
163+
164+
165+
@patch("data_designer.cli.commands.reset.ModelRepository")
166+
@patch("data_designer.cli.commands.reset.ProviderRepository")
167+
@patch("data_designer.cli.commands.reset.confirm_action")
168+
def test_reset_mixed_confirmation(
169+
mock_confirm: Mock,
170+
mock_provider_repo: Mock,
171+
mock_model_repo: Mock,
172+
mock_repositories_factory: MockRepositoryFactory,
173+
) -> None:
174+
"""Test reset when user confirms one file but not the other."""
175+
_, mock_provider_instance, _, mock_model_instance = mock_repositories_factory(
176+
provider_exists=True, model_exists=True
177+
)
178+
mock_provider_repo.return_value = mock_provider_instance
179+
mock_model_repo.return_value = mock_model_instance
180+
mock_confirm.side_effect = [True, False]
181+
182+
reset_command(config_dir=None)
183+
184+
assert mock_confirm.call_count == 2
185+
mock_provider_instance.delete.assert_called_once()
186+
mock_model_instance.delete.assert_not_called()
187+
188+
189+
@patch("data_designer.cli.commands.reset.ModelRepository")
190+
@patch("data_designer.cli.commands.reset.ProviderRepository")
191+
def test_reset_with_custom_config_dir(
192+
mock_provider_repo: Mock,
193+
mock_model_repo: Mock,
194+
mock_repositories_factory: MockRepositoryFactory,
195+
) -> None:
196+
"""Test reset with a custom configuration directory."""
197+
custom_dir = "/custom/config/path"
198+
_, mock_provider_instance, _, mock_model_instance = mock_repositories_factory(
199+
provider_exists=False, model_exists=False
200+
)
201+
mock_provider_repo.return_value = mock_provider_instance
202+
mock_model_repo.return_value = mock_model_instance
203+
204+
with pytest.raises(typer.Exit):
205+
reset_command(config_dir=custom_dir)
206+
207+
expected_path = Path(custom_dir).expanduser().resolve()
208+
mock_provider_repo.assert_called_once_with(expected_path)
209+
mock_model_repo.assert_called_once_with(expected_path)
210+
211+
212+
@pytest.mark.parametrize(
213+
"provider_error,model_error,expected_provider_calls,expected_model_calls",
214+
[
215+
(Exception("Permission denied"), None, 1, 1),
216+
(None, OSError("Disk error"), 1, 1),
217+
(Exception("Error 1"), Exception("Error 2"), 1, 1),
218+
],
219+
ids=["provider_fails", "model_fails", "both_fail"],
220+
)
221+
@patch("data_designer.cli.commands.reset.ModelRepository")
222+
@patch("data_designer.cli.commands.reset.ProviderRepository")
223+
@patch("data_designer.cli.commands.reset.confirm_action")
224+
def test_reset_deletion_failures(
225+
mock_confirm: Mock,
226+
mock_provider_repo: Mock,
227+
mock_model_repo: Mock,
228+
mock_repositories_factory: MockRepositoryFactory,
229+
provider_error: Exception | None,
230+
model_error: Exception | None,
231+
expected_provider_calls: int,
232+
expected_model_calls: int,
233+
) -> None:
234+
"""Test reset when deletion fails for one or more repositories."""
235+
_, mock_provider_instance, _, mock_model_instance = mock_repositories_factory(
236+
provider_exists=True,
237+
model_exists=True,
238+
provider_delete_error=provider_error,
239+
model_delete_error=model_error,
240+
)
241+
mock_provider_repo.return_value = mock_provider_instance
242+
mock_model_repo.return_value = mock_model_instance
243+
mock_confirm.return_value = True
244+
245+
with pytest.raises(typer.Exit) as exc_info:
246+
reset_command(config_dir=None)
247+
248+
assert exc_info.value.exit_code == 1
249+
assert mock_provider_instance.delete.call_count == expected_provider_calls
250+
assert mock_model_instance.delete.call_count == expected_model_calls
251+
252+
253+
@pytest.mark.parametrize(
254+
"provider_exists,model_exists,expected_confirms,expected_provider_deletes,expected_model_deletes",
255+
[
256+
(True, False, 1, 1, 0),
257+
(False, True, 1, 0, 1),
258+
],
259+
ids=["only_provider", "only_model"],
260+
)
261+
@patch("data_designer.cli.commands.reset.ModelRepository")
262+
@patch("data_designer.cli.commands.reset.ProviderRepository")
263+
@patch("data_designer.cli.commands.reset.confirm_action")
264+
def test_reset_single_file_exists(
265+
mock_confirm: Mock,
266+
mock_provider_repo: Mock,
267+
mock_model_repo: Mock,
268+
mock_repositories_factory: MockRepositoryFactory,
269+
provider_exists: bool,
270+
model_exists: bool,
271+
expected_confirms: int,
272+
expected_provider_deletes: int,
273+
expected_model_deletes: int,
274+
) -> None:
275+
"""Test reset when only one config file exists."""
276+
_, mock_provider_instance, _, mock_model_instance = mock_repositories_factory(
277+
provider_exists=provider_exists, model_exists=model_exists
278+
)
279+
mock_provider_repo.return_value = mock_provider_instance
280+
mock_model_repo.return_value = mock_model_instance
281+
mock_confirm.return_value = True
282+
283+
reset_command(config_dir=None)
284+
285+
assert mock_confirm.call_count == expected_confirms
286+
assert mock_provider_instance.delete.call_count == expected_provider_deletes
287+
assert mock_model_instance.delete.call_count == expected_model_deletes
288+
289+
290+
@patch("data_designer.cli.commands.reset.ModelRepository")
291+
@patch("data_designer.cli.commands.reset.ProviderRepository")
292+
@patch("data_designer.cli.commands.reset.confirm_action")
293+
def test_reset_uses_default_config_dir_when_none_provided(
294+
mock_confirm: Mock,
295+
mock_provider_repo: Mock,
296+
mock_model_repo: Mock,
297+
mock_repositories_factory: MockRepositoryFactory,
298+
) -> None:
299+
"""Test that default config directory is used when config_dir is None."""
300+
_, mock_provider_instance, _, mock_model_instance = mock_repositories_factory(
301+
provider_exists=False, model_exists=False
302+
)
303+
mock_provider_repo.return_value = mock_provider_instance
304+
mock_model_repo.return_value = mock_model_instance
305+
306+
with pytest.raises(typer.Exit):
307+
reset_command(config_dir=None)
308+
309+
mock_provider_repo.assert_called_once_with(DEFAULT_CONFIG_DIR)
310+
mock_model_repo.assert_called_once_with(DEFAULT_CONFIG_DIR)

0 commit comments

Comments
 (0)