Skip to content

Commit d3efb44

Browse files
authored
Merge pull request #32 from zhujian0805/main
fix: resolve failing tests in CLI integration and plugin commands
2 parents b20d78e + faed8b5 commit d3efb44

File tree

8 files changed

+45
-276
lines changed

8 files changed

+45
-276
lines changed

README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,6 @@ cam plugin install <plugin>[@marketplace] # Install a plugin from marketplace
275275
cam plugin uninstall <plugin> # Uninstall a plugin
276276
cam plugin enable <plugin> # Enable a disabled plugin
277277
cam plugin disable <plugin> # Disable an enabled plugin
278-
cam plugin browse [marketplace] # Browse plugins in marketplaces
279278
cam plugin view <plugin> # View detailed plugin information
280279
cam plugin status # Show plugin system status
281280
cam plugin validate <path> # Validate plugin/marketplace manifest

code_assistant_manager/cli/app.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -309,19 +309,21 @@ def validate_config(
309309
typer.echo(f"{Colors.RED}✗ Configuration validation failed:{Colors.RESET}")
310310
for error in errors:
311311
typer.echo(f" - {error}")
312-
return 1
312+
raise typer.Exit(1)
313313

314+
except typer.Exit:
315+
raise
314316
except FileNotFoundError as e:
315317
typer.echo(f"{Colors.RED}✗ Configuration file not found: {e}{Colors.RESET}")
316-
return 1
318+
raise typer.Exit(1)
317319
except ValueError as e:
318320
typer.echo(f"{Colors.RED}✗ Configuration validation failed: {e}{Colors.RESET}")
319-
return 1
321+
raise typer.Exit(1)
320322
except Exception as e:
321323
typer.echo(
322324
f"{Colors.RED}✗ Unexpected error during validation: {e}{Colors.RESET}"
323325
)
324-
return 1
326+
raise typer.Exit(1)
325327

326328

327329
@config_app.command("list", short_help="List all configuration file locations")

code_assistant_manager/cli/plugin_commands.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,5 @@
4343
plugin_app.command("validate")(plugin_install_commands.validate_plugin)
4444

4545
# Discovery commands
46-
plugin_app.command("browse")(plugin_discovery_commands.browse_marketplace)
4746
plugin_app.command("view")(plugin_discovery_commands.view_plugin)
4847
plugin_app.command("status")(plugin_discovery_commands.plugin_status)

code_assistant_manager/cli/plugins/plugin_discovery_commands.py

Lines changed: 3 additions & 187 deletions
Original file line numberDiff line numberDiff line change
@@ -160,190 +160,6 @@ def _resolve_from_known_marketplaces(
160160
return None, None, "main"
161161

162162

163-
@plugin_app.command("browse")
164-
def browse_marketplace(
165-
marketplace: Optional[str] = typer.Argument(
166-
None,
167-
help="Marketplace name to browse (from 'cam plugin repos'). If not specified, shows all plugins from all marketplaces.",
168-
),
169-
query: Optional[str] = typer.Option(
170-
None,
171-
"--query",
172-
"-q",
173-
help="Filter plugins by name or description",
174-
),
175-
category: Optional[str] = typer.Option(
176-
None,
177-
"--category",
178-
"-c",
179-
help="Filter plugins by category",
180-
),
181-
limit: int = typer.Option(
182-
50,
183-
"--limit",
184-
"-n",
185-
help="Maximum number of plugins to show",
186-
),
187-
app_type: str = typer.Option(
188-
"claude",
189-
"--app",
190-
"-a",
191-
help=f"App type ({', '.join(VALID_APP_TYPES)})",
192-
),
193-
):
194-
"""[DEPRECATED] Browse plugins in configured marketplaces or a specific one.
195-
196-
⚠️ This command is deprecated. Use 'cam plugin list' instead.
197-
198-
Without a marketplace name: Shows all plugins from all marketplaces and standalone plugins.
199-
With a marketplace name: Fetches the marketplace manifest from GitHub and lists available plugins.
200-
Use --query to search by name/description, --category to filter by category.
201-
"""
202-
from code_assistant_manager.cli.option_utils import resolve_single_app
203-
from code_assistant_manager.plugins.fetch import fetch_repo_info
204-
205-
# Show deprecation warning
206-
typer.echo(f"{Colors.YELLOW}⚠️ Warning: 'cam plugin browse' is deprecated.{Colors.RESET}")
207-
typer.echo(f"{Colors.CYAN} Use 'cam plugin list' instead to view installed and available plugins.{Colors.RESET}")
208-
typer.echo()
209-
210-
app = resolve_single_app(app_type, VALID_APP_TYPES, default="claude")
211-
manager = PluginManager()
212-
handler = get_handler(app)
213-
214-
# If no marketplace specified, show all plugins from all repos
215-
if not marketplace:
216-
typer.echo(
217-
f"{Colors.CYAN}Fetching all plugins from all marketplaces and plugins...{Colors.RESET}\n"
218-
)
219-
220-
all_repos = manager.get_all_repos()
221-
if not all_repos:
222-
typer.echo(
223-
f"{Colors.YELLOW}No plugin repositories configured{Colors.RESET}"
224-
)
225-
return
226-
227-
all_plugins = []
228-
repo_sources = {} # Track which repo each plugin comes from
229-
230-
for repo_name, repo in all_repos.items():
231-
if not repo.repo_owner or not repo.repo_name:
232-
continue
233-
234-
# Fetch repo info
235-
info = fetch_repo_info(
236-
repo.repo_owner, repo.repo_name, repo.repo_branch or "main"
237-
)
238-
if not info:
239-
typer.echo(
240-
f" {Colors.YELLOW}{Colors.RESET} {repo_name} (failed to fetch)"
241-
)
242-
continue
243-
244-
if info.type == "marketplace":
245-
# Add plugins from marketplace with their source
246-
for plugin in info.plugins:
247-
plugin["marketplace"] = repo_name
248-
repo_sources[f"{plugin.get('name', '')}@{repo_name}"] = repo_name
249-
all_plugins.extend(info.plugins)
250-
else:
251-
# Single plugin repository
252-
plugin_name = info.name
253-
all_plugins.append(
254-
{
255-
"name": plugin_name,
256-
"version": info.version or "",
257-
"description": info.description or "",
258-
"category": "",
259-
"marketplace": repo_name,
260-
}
261-
)
262-
repo_sources[f"{plugin_name}@{repo_name}"] = repo_name
263-
264-
if not all_plugins:
265-
typer.echo(
266-
f"{Colors.YELLOW}No plugins found in configured repositories{Colors.RESET}"
267-
)
268-
return
269-
270-
# Filter and display
271-
plugins = _filter_plugins(all_plugins, query, category)
272-
total = len(plugins)
273-
274-
typer.echo(f"{Colors.BOLD}All Available Plugins:{Colors.RESET}")
275-
typer.echo(f"Total: {total} plugins\n")
276-
277-
if query or category:
278-
typer.echo(f"Matching: {total}\n")
279-
280-
# Organize plugins by marketplace
281-
plugins_by_marketplace = {}
282-
for plugin in plugins:
283-
marketplace_name = plugin.get("marketplace", "Unknown")
284-
if marketplace_name not in plugins_by_marketplace:
285-
plugins_by_marketplace[marketplace_name] = []
286-
plugins_by_marketplace[marketplace_name].append(plugin)
287-
288-
# Display plugins organized by marketplace
289-
displayed_count = 0
290-
for marketplace_name in sorted(plugins_by_marketplace.keys()):
291-
marketplace_plugins = plugins_by_marketplace[marketplace_name]
292-
typer.echo(f"\n{Colors.BOLD}{marketplace_name}:{Colors.RESET}\n")
293-
294-
for plugin in marketplace_plugins:
295-
if displayed_count >= limit:
296-
break
297-
_display_plugin(plugin)
298-
displayed_count += 1
299-
300-
if displayed_count >= limit:
301-
break
302-
303-
if total > limit:
304-
typer.echo(f"\n ... and {total - limit} more")
305-
306-
categories = {p.get("category") for p in all_plugins if p.get("category")}
307-
if categories:
308-
typer.echo(
309-
f"\n{Colors.CYAN}Categories:{Colors.RESET} {', '.join(sorted(categories))}"
310-
)
311-
312-
typer.echo(
313-
f"\n{Colors.CYAN}Filter by marketplace:{Colors.RESET} cam plugin browse <marketplace-name>"
314-
)
315-
typer.echo()
316-
return
317-
318-
# Resolve marketplace to repo info
319-
repo_owner, repo_name, repo_branch = _resolve_marketplace_repo(
320-
manager, handler, marketplace
321-
)
322-
323-
if not repo_owner or not repo_name:
324-
_display_marketplace_not_found(manager, handler, marketplace)
325-
raise typer.Exit(1)
326-
327-
# Fetch plugins
328-
typer.echo(f"{Colors.CYAN}Fetching plugins from {marketplace}...{Colors.RESET}")
329-
info = fetch_repo_info(repo_owner, repo_name, repo_branch)
330-
331-
if not info or not info.plugins:
332-
typer.echo(f"{Colors.RED}✗ Could not fetch plugins from repo.{Colors.RESET}")
333-
raise typer.Exit(1)
334-
335-
# Filter and display
336-
plugins = _filter_plugins(info.plugins, query, category)
337-
total = len(plugins)
338-
plugins = plugins[:limit]
339-
340-
_display_marketplace_header(info, query, category, total)
341-
typer.echo(f"\n{Colors.BOLD}Plugins:{Colors.RESET}\n")
342-
343-
for plugin in plugins:
344-
_display_plugin(plugin)
345-
346-
_display_marketplace_footer(info, marketplace, total, limit)
347163

348164

349165
@plugin_app.command("view")
@@ -431,11 +247,11 @@ def view_plugin(
431247
f"{Colors.RED}✗ Plugin '{plugin}' not found in configured repositories{Colors.RESET}"
432248
)
433249
typer.echo()
434-
typer.echo(f"{Colors.CYAN}Try browsing available plugins:{Colors.RESET}")
250+
typer.echo(f"{Colors.CYAN}Try listing available plugins:{Colors.RESET}")
435251
if marketplace_filter:
436-
typer.echo(f" cam plugin browse {marketplace_filter}")
252+
typer.echo(f" cam plugin list {marketplace_filter}")
437253
else:
438-
typer.echo(f" cam plugin browse")
254+
typer.echo(f" cam plugin list")
439255
raise typer.Exit(1)
440256

441257
# Display plugin details

code_assistant_manager/cli/plugins/plugin_install_commands.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ def _resolve_plugin_conflict(plugin_name: str, app_type: str) -> str:
118118
typer.echo(f"\n{Colors.YELLOW}Unreachable marketplaces (temporarily unavailable):{Colors.RESET}")
119119
for unreachable in unreachable_marketplaces:
120120
typer.echo(f" • {unreachable['marketplace']} ({unreachable['source']})")
121-
typer.echo(f"\n{Colors.CYAN}Browse plugins:{Colors.RESET} cam plugin browse")
121+
typer.echo(f"\n{Colors.CYAN}Browse plugins:{Colors.RESET} cam plugin list")
122122
raise typer.Exit(1)
123123

124124
elif len(found_in_marketplaces) == 1:
@@ -367,7 +367,7 @@ def install_plugin(
367367
- marketplace-name:plugin-name (specifies which marketplace to use)
368368
369369
For marketplace management, use 'cam plugin marketplace install <marketplace>'.
370-
For browsing available plugins, use 'cam plugin browse'.
370+
For browsing available plugins, use 'cam plugin list'.
371371
372372
Examples:
373373
cam plugin install code-reviewer

code_assistant_manager/cli/plugins/plugin_marketplace_commands.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -478,7 +478,7 @@ def marketplace_install(
478478
to the target AI assistant app(s). The marketplace must already be configured
479479
using 'cam plugin marketplace add' before it can be installed.
480480
481-
After installation, you can browse plugins with 'cam plugin browse <marketplace>'
481+
After installation, you can browse plugins with 'cam plugin list <marketplace>'
482482
and install them with 'cam plugin install <plugin>@<marketplace>'.
483483
484484
Examples:
@@ -578,7 +578,7 @@ def marketplace_install(
578578
if len(target_apps) == 1:
579579
if installed_count > 0 or already_installed_count > 0:
580580
typer.echo(
581-
f"\n{Colors.CYAN}Browse plugins with:{Colors.RESET} cam plugin marketplace browse <marketplace>"
581+
f"\n{Colors.CYAN}Browse plugins with:{Colors.RESET} cam plugin list <marketplace>"
582582
)
583583
typer.echo(
584584
f"{Colors.CYAN}Install plugins with:{Colors.RESET} cam plugin install <plugin-name>@<marketplace>"

tests/test_cli_integration_comprehensive.py

Lines changed: 31 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -160,50 +160,58 @@ def test_config_validate_failure(self, mock_config_class, runner):
160160
assert "✗ Configuration validation failed" in result.output
161161

162162
def test_config_list_locations(self, runner):
163-
"""Test config list-locations command."""
164-
result = runner.invoke(app, ["config", "list-locations"])
163+
"""Test config list command."""
164+
result = runner.invoke(app, ["config", "list"])
165165
assert result.exit_code == 0
166166
assert "Configuration Files" in result.output
167167

168-
@patch("code_assistant_manager.cli.app.load_app_config")
169-
def test_config_show_command(self, mock_load, runner):
168+
@patch("code_assistant_manager.configs.get_tool_config")
169+
def test_config_show_command(self, mock_get_tool_config, runner):
170170
"""Test config show command."""
171-
mock_load.return_value = ({"key": "value"}, "/tmp/config.json")
171+
mock_tool_config = MagicMock()
172+
mock_tool_config.load_config.return_value = {"user": {"data": {"key": "value"}, "path": "/tmp/config.json"}}
173+
mock_get_tool_config.return_value = mock_tool_config
172174

173175
result = runner.invoke(app, ["config", "show"])
174176
assert result.exit_code == 0
175177
assert "value" in result.output
176178

177-
@patch("code_assistant_manager.cli.app.load_app_config")
178-
def test_config_show_specific_key(self, runner, mock_load):
179+
@patch("code_assistant_manager.configs.get_tool_config")
180+
def test_config_show_specific_key(self, mock_get_tool_config, runner):
179181
"""Test config show command with specific key."""
180-
mock_load.return_value = ({"app.key": "value"}, "/tmp/config.json")
182+
mock_tool_config = MagicMock()
183+
mock_tool_config.load_config.return_value = {"user": {"data": {"key": "value"}, "path": "/tmp/config.json"}}
184+
mock_get_tool_config.return_value = mock_tool_config
181185

182-
result = runner.invoke(app, ["config", "show", "app.key"])
186+
result = runner.invoke(app, ["config", "show", "claude.key"])
183187
assert result.exit_code == 0
184188
assert "value" in result.output
185189

186-
@patch("code_assistant_manager.cli.app.load_app_config")
187-
def test_config_show_wildcard(self, runner, mock_load):
190+
@patch("code_assistant_manager.configs.get_tool_config")
191+
def test_config_show_wildcard(self, mock_get_tool_config, runner):
188192
"""Test config show command with wildcard."""
189-
mock_load.return_value = ({"app.key1": "value1", "app.key2": "value2"}, "/tmp/config.json")
190-
191-
@patch("code_assistant_manager.config.ConfigManager")
192-
def test_config_set_command(self, runner, mock_config_class):
193+
mock_tool_config = MagicMock()
194+
mock_tool_config.load_config.return_value = {
195+
"user": {"data": {"app.key1": "value1", "app.key2": "value2"}, "path": "/tmp/config.json"}
196+
}
197+
mock_get_tool_config.return_value = mock_tool_config
198+
199+
@patch("code_assistant_manager.configs.get_tool_config")
200+
def test_config_set_command(self, mock_get_tool_config, runner):
193201
"""Test config set command."""
194-
mock_config = MagicMock()
195-
mock_config.set_value.return_value = None
196-
mock_config_class.return_value = mock_config
202+
mock_tool_config = MagicMock()
203+
mock_tool_config.set_value.return_value = "/tmp/config.json"
204+
mock_get_tool_config.return_value = mock_tool_config
197205

198206
result = runner.invoke(app, ["config", "set", "test.key=value"])
199207
assert result.exit_code == 0
200208

201-
@patch("code_assistant_manager.config.ConfigManager")
202-
def test_config_unset_command(self, runner, mock_config_class):
209+
@patch("code_assistant_manager.configs.get_tool_config")
210+
def test_config_unset_command(self, mock_get_tool_config, runner):
203211
"""Test config unset command."""
204-
mock_config = MagicMock()
205-
mock_config.unset_value.return_value = None
206-
mock_config_class.return_value = mock_config
212+
mock_tool_config = MagicMock()
213+
mock_tool_config.unset_value.return_value = True
214+
mock_get_tool_config.return_value = mock_tool_config
207215

208216
result = runner.invoke(app, ["config", "unset", "test.key"])
209217
assert result.exit_code == 0
@@ -285,14 +293,6 @@ def test_plugin_validate(self, runner, mock_validate):
285293
assert result.exit_code == 0
286294
mock_validate.assert_called_once()
287295

288-
@patch("code_assistant_manager.cli.plugins.plugin_discovery_commands.browse_marketplace")
289-
def test_plugin_browse(self, runner, mock_browse):
290-
"""Test plugin browse command."""
291-
mock_browse.return_value = []
292-
293-
result = runner.invoke(app, ["plugin", "browse"])
294-
assert result.exit_code == 0
295-
mock_browse.assert_called_once()
296296

297297
@patch("code_assistant_manager.cli.plugins.plugin_discovery_commands.view_plugin")
298298
def test_plugin_view(self, runner, mock_view):
@@ -348,15 +348,6 @@ def test_plugin_disable(self, runner, mock_disable):
348348
assert result.exit_code == 0
349349
mock_disable.assert_called_once()
350350

351-
@patch("code_assistant_manager.cli.plugins.plugin_discovery_commands.browse_marketplace")
352-
def test_plugin_marketplace_browse(self, runner, mock_browse):
353-
"""Test plugin marketplace browse command."""
354-
mock_browse.return_value = []
355-
356-
result = runner.invoke(app, ["plugin", "marketplace", "browse"])
357-
assert result.exit_code == 0
358-
mock_browse.assert_called_once()
359-
360351

361352
class TestAgentCommands:
362353
"""Test agent command subcommands."""

0 commit comments

Comments
 (0)