Skip to content

Commit ce7893a

Browse files
committed
test: add unit tests for manage_packages tool and CLI
1 parent 1000228 commit ce7893a

1 file changed

Lines changed: 276 additions & 0 deletions

File tree

Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
"""Tests for manage_packages tool and CLI commands."""
2+
3+
import asyncio
4+
import pytest
5+
from unittest.mock import patch, MagicMock, AsyncMock
6+
from click.testing import CliRunner
7+
8+
from cli.commands.packages import packages
9+
from cli.utils.config import CLIConfig
10+
from services.tools.manage_packages import ALL_ACTIONS
11+
12+
13+
# =============================================================================
14+
# Fixtures
15+
# =============================================================================
16+
17+
@pytest.fixture
18+
def runner():
19+
return CliRunner()
20+
21+
22+
@pytest.fixture
23+
def mock_config():
24+
return CLIConfig(
25+
host="127.0.0.1",
26+
port=8080,
27+
timeout=30,
28+
format="text",
29+
unity_instance=None,
30+
)
31+
32+
33+
@pytest.fixture
34+
def mock_success():
35+
return {"success": True, "message": "OK", "data": {}}
36+
37+
38+
# =============================================================================
39+
# Action Lists
40+
# =============================================================================
41+
42+
class TestActionLists:
43+
"""Verify action list completeness and consistency."""
44+
45+
def test_all_actions_is_not_empty(self):
46+
assert len(ALL_ACTIONS) > 0
47+
48+
def test_no_duplicate_actions(self):
49+
assert len(ALL_ACTIONS) == len(set(ALL_ACTIONS))
50+
51+
def test_expected_query_actions_present(self):
52+
expected = {"list_packages", "search_packages", "get_package_info", "ping", "status"}
53+
assert expected.issubset(set(ALL_ACTIONS))
54+
55+
def test_expected_install_remove_actions_present(self):
56+
expected = {"add_package", "remove_package", "embed_package", "resolve_packages"}
57+
assert expected.issubset(set(ALL_ACTIONS))
58+
59+
def test_expected_registry_actions_present(self):
60+
expected = {"add_registry", "remove_registry", "list_registries"}
61+
assert expected.issubset(set(ALL_ACTIONS))
62+
63+
64+
# =============================================================================
65+
# Tool Validation (Python-side, no Unity)
66+
# =============================================================================
67+
68+
class TestManagePackagesToolValidation:
69+
"""Test action validation in the manage_packages tool function."""
70+
71+
def test_unknown_action_returns_error(self):
72+
from services.tools.manage_packages import manage_packages
73+
74+
ctx = MagicMock()
75+
ctx.get_state = AsyncMock(return_value=None)
76+
77+
result = asyncio.run(manage_packages(ctx, action="invalid_action"))
78+
assert result["success"] is False
79+
assert "Unknown action" in result["message"]
80+
81+
def test_unknown_action_lists_valid_actions(self):
82+
from services.tools.manage_packages import manage_packages
83+
84+
ctx = MagicMock()
85+
ctx.get_state = AsyncMock(return_value=None)
86+
87+
result = asyncio.run(manage_packages(ctx, action="bogus"))
88+
assert result["success"] is False
89+
# The error message should mention the valid actions
90+
assert "add_package" in result["message"] or "Valid actions" in result["message"]
91+
92+
def test_action_matching_is_case_insensitive(self):
93+
from services.tools.manage_packages import manage_packages
94+
95+
ctx = MagicMock()
96+
ctx.get_state = AsyncMock(return_value=None)
97+
98+
# Should not return "Unknown action" — it should try to send to Unity
99+
# We patch _send_packages_command to avoid actually connecting
100+
with patch("services.tools.manage_packages._send_packages_command", new_callable=AsyncMock) as mock_send:
101+
mock_send.return_value = {"success": True, "message": "OK"}
102+
result = asyncio.run(manage_packages(ctx, action="LIST_PACKAGES"))
103+
assert result["success"] is True
104+
105+
106+
# =============================================================================
107+
# CLI Command Parameter Building
108+
# =============================================================================
109+
110+
def _get_params(mock_run):
111+
"""Helper to extract the params dict from a mock run_command call."""
112+
return mock_run.call_args[0][1]
113+
114+
115+
class TestPackagesQueryCLICommands:
116+
"""Verify query CLI commands build correct parameter dicts."""
117+
118+
def test_list_builds_correct_params(self, runner, mock_config, mock_success):
119+
with patch("cli.commands.packages.get_config", return_value=mock_config):
120+
with patch("cli.commands.packages.run_command", return_value=mock_success) as mock_run:
121+
runner.invoke(packages, ["list"])
122+
123+
mock_run.assert_called_once()
124+
params = _get_params(mock_run)
125+
assert params["action"] == "list_packages"
126+
127+
def test_search_builds_correct_params(self, runner, mock_config, mock_success):
128+
with patch("cli.commands.packages.get_config", return_value=mock_config):
129+
with patch("cli.commands.packages.run_command", return_value=mock_success) as mock_run:
130+
runner.invoke(packages, ["search", "input"])
131+
132+
params = _get_params(mock_run)
133+
assert params["action"] == "search_packages"
134+
assert params["query"] == "input"
135+
136+
def test_info_builds_correct_params(self, runner, mock_config, mock_success):
137+
with patch("cli.commands.packages.get_config", return_value=mock_config):
138+
with patch("cli.commands.packages.run_command", return_value=mock_success) as mock_run:
139+
runner.invoke(packages, ["info", "com.unity.inputsystem"])
140+
141+
params = _get_params(mock_run)
142+
assert params["action"] == "get_package_info"
143+
assert params["package"] == "com.unity.inputsystem"
144+
145+
def test_ping_builds_correct_params(self, runner, mock_config, mock_success):
146+
with patch("cli.commands.packages.get_config", return_value=mock_config):
147+
with patch("cli.commands.packages.run_command", return_value=mock_success) as mock_run:
148+
runner.invoke(packages, ["ping"])
149+
150+
params = _get_params(mock_run)
151+
assert params["action"] == "ping"
152+
153+
def test_status_without_job_id(self, runner, mock_config, mock_success):
154+
with patch("cli.commands.packages.get_config", return_value=mock_config):
155+
with patch("cli.commands.packages.run_command", return_value=mock_success) as mock_run:
156+
runner.invoke(packages, ["status"])
157+
158+
params = _get_params(mock_run)
159+
assert params["action"] == "status"
160+
assert "job_id" not in params
161+
162+
def test_status_with_job_id(self, runner, mock_config, mock_success):
163+
with patch("cli.commands.packages.get_config", return_value=mock_config):
164+
with patch("cli.commands.packages.run_command", return_value=mock_success) as mock_run:
165+
runner.invoke(packages, ["status", "abc123"])
166+
167+
params = _get_params(mock_run)
168+
assert params["action"] == "status"
169+
assert params["job_id"] == "abc123"
170+
171+
172+
class TestPackagesInstallRemoveCLICommands:
173+
"""Verify install/remove CLI commands build correct parameter dicts."""
174+
175+
def test_add_builds_correct_params(self, runner, mock_config, mock_success):
176+
with patch("cli.commands.packages.get_config", return_value=mock_config):
177+
with patch("cli.commands.packages.run_command", return_value=mock_success) as mock_run:
178+
runner.invoke(packages, ["add", "com.unity.inputsystem"])
179+
180+
params = _get_params(mock_run)
181+
assert params["action"] == "add_package"
182+
assert params["package"] == "com.unity.inputsystem"
183+
184+
def test_add_with_version_builds_correct_params(self, runner, mock_config, mock_success):
185+
with patch("cli.commands.packages.get_config", return_value=mock_config):
186+
with patch("cli.commands.packages.run_command", return_value=mock_success) as mock_run:
187+
runner.invoke(packages, ["add", "com.unity.inputsystem@1.8.0"])
188+
189+
params = _get_params(mock_run)
190+
assert params["action"] == "add_package"
191+
assert params["package"] == "com.unity.inputsystem@1.8.0"
192+
193+
def test_remove_builds_correct_params(self, runner, mock_config, mock_success):
194+
with patch("cli.commands.packages.get_config", return_value=mock_config):
195+
with patch("cli.commands.packages.run_command", return_value=mock_success) as mock_run:
196+
runner.invoke(packages, ["remove", "com.unity.inputsystem"])
197+
198+
params = _get_params(mock_run)
199+
assert params["action"] == "remove_package"
200+
assert params["package"] == "com.unity.inputsystem"
201+
assert "force" not in params
202+
203+
def test_remove_with_force_builds_correct_params(self, runner, mock_config, mock_success):
204+
with patch("cli.commands.packages.get_config", return_value=mock_config):
205+
with patch("cli.commands.packages.run_command", return_value=mock_success) as mock_run:
206+
runner.invoke(packages, ["remove", "com.unity.inputsystem", "--force"])
207+
208+
params = _get_params(mock_run)
209+
assert params["action"] == "remove_package"
210+
assert params["force"] is True
211+
212+
def test_embed_builds_correct_params(self, runner, mock_config, mock_success):
213+
with patch("cli.commands.packages.get_config", return_value=mock_config):
214+
with patch("cli.commands.packages.run_command", return_value=mock_success) as mock_run:
215+
runner.invoke(packages, ["embed", "com.unity.timeline"])
216+
217+
params = _get_params(mock_run)
218+
assert params["action"] == "embed_package"
219+
assert params["package"] == "com.unity.timeline"
220+
221+
def test_resolve_builds_correct_params(self, runner, mock_config, mock_success):
222+
with patch("cli.commands.packages.get_config", return_value=mock_config):
223+
with patch("cli.commands.packages.run_command", return_value=mock_success) as mock_run:
224+
runner.invoke(packages, ["resolve"])
225+
226+
params = _get_params(mock_run)
227+
assert params["action"] == "resolve_packages"
228+
229+
230+
class TestRegistryCLICommands:
231+
"""Verify registry CLI commands build correct parameter dicts."""
232+
233+
def test_list_registries_builds_correct_params(self, runner, mock_config, mock_success):
234+
with patch("cli.commands.packages.get_config", return_value=mock_config):
235+
with patch("cli.commands.packages.run_command", return_value=mock_success) as mock_run:
236+
runner.invoke(packages, ["list-registries"])
237+
238+
params = _get_params(mock_run)
239+
assert params["action"] == "list_registries"
240+
241+
def test_add_registry_builds_correct_params(self, runner, mock_config, mock_success):
242+
with patch("cli.commands.packages.get_config", return_value=mock_config):
243+
with patch("cli.commands.packages.run_command", return_value=mock_success) as mock_run:
244+
runner.invoke(packages, [
245+
"add-registry", "OpenUPM",
246+
"--url", "https://package.openupm.com",
247+
"--scope", "com.cysharp",
248+
"--scope", "com.neuecc",
249+
])
250+
251+
params = _get_params(mock_run)
252+
assert params["action"] == "add_registry"
253+
assert params["name"] == "OpenUPM"
254+
assert params["url"] == "https://package.openupm.com"
255+
assert params["scopes"] == ["com.cysharp", "com.neuecc"]
256+
257+
def test_add_registry_with_single_scope(self, runner, mock_config, mock_success):
258+
with patch("cli.commands.packages.get_config", return_value=mock_config):
259+
with patch("cli.commands.packages.run_command", return_value=mock_success) as mock_run:
260+
runner.invoke(packages, [
261+
"add-registry", "MyReg",
262+
"--url", "https://registry.example.com",
263+
"--scope", "com.example",
264+
])
265+
266+
params = _get_params(mock_run)
267+
assert params["scopes"] == ["com.example"]
268+
269+
def test_remove_registry_builds_correct_params(self, runner, mock_config, mock_success):
270+
with patch("cli.commands.packages.get_config", return_value=mock_config):
271+
with patch("cli.commands.packages.run_command", return_value=mock_success) as mock_run:
272+
runner.invoke(packages, ["remove-registry", "OpenUPM"])
273+
274+
params = _get_params(mock_run)
275+
assert params["action"] == "remove_registry"
276+
assert params["name"] == "OpenUPM"

0 commit comments

Comments
 (0)