From 6f46d2ece25fc49de30ef3bb6d3d22bbf7b4c168 Mon Sep 17 00:00:00 2001 From: Test Improver Date: Wed, 15 Apr 2026 02:38:52 +0000 Subject: [PATCH] test: expand view command coverage (66% -> 95%) Add 14 new tests covering: - Rich panel rendering paths (display_package_info, display_versions) - Lockfile ref/commit display (_lookup_lockfile_ref, lines 107-118) - Context file and workflow counts in both Rich and plain-text paths - Hook file display in both Rich and plain-text paths - --global flag scope resolution (lines 354-356) - Global scope missing apm_modules/ error handling Coverage: 66% -> 95% (195 stmts, 9 remaining in exception guards) Tests: 19 -> 33 (+14) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tests/unit/test_view_command.py | 249 ++++++++++++++++++++++++++++++++ 1 file changed, 249 insertions(+) diff --git a/tests/unit/test_view_command.py b/tests/unit/test_view_command.py index 83b0f548..33cf9ef9 100644 --- a/tests/unit/test_view_command.py +++ b/tests/unit/test_view_command.py @@ -358,6 +358,255 @@ def test_view_rejects_dot_segment(self): assert "traversal" in result.output.lower() +class TestViewCommandRichDisplay(_InfoCmdBase): + """Tests for Rich rendering paths in ``apm view``.""" + + def test_view_rich_panel_display(self): + """``apm view`` renders a Rich panel when Rich is available.""" + with self._chdir_tmp() as tmp: + self._make_package( + tmp, "richorg", "richrepo", version="3.0.0", + description="Rich panel test", author="Carol", + ) + os.chdir(tmp) + # No _force_rich_fallback -- let Rich render + result = self.runner.invoke(cli, ["view", "richorg/richrepo"]) + assert result.exit_code == 0 + assert "3.0.0" in result.output + assert "Rich panel test" in result.output + assert "Carol" in result.output + + def test_view_rich_panel_with_hooks(self): + """``apm view`` Rich panel includes hook count when hooks exist.""" + with self._chdir_tmp() as tmp: + pkg_dir = self._make_package(tmp, "hookorg", "hookrepo") + hooks_dir = pkg_dir / "hooks" + hooks_dir.mkdir() + (hooks_dir / "pre-commit.json").write_text('{"hooks": []}') + os.chdir(tmp) + result = self.runner.invoke(cli, ["view", "hookorg/hookrepo"]) + assert result.exit_code == 0 + assert "hook" in result.output.lower() + + def test_view_rich_panel_with_context_files(self): + """``apm view`` Rich panel shows context file count when present.""" + with self._chdir_tmp() as tmp: + pkg_dir = self._make_package(tmp, "ctxorg", "ctxrepo") + instr_dir = pkg_dir / ".apm" / "instructions" + instr_dir.mkdir(parents=True) + (instr_dir / "coding-style.md").write_text("# Style guide") + os.chdir(tmp) + result = self.runner.invoke(cli, ["view", "ctxorg/ctxrepo"]) + assert result.exit_code == 0 + assert "instructions" in result.output.lower() or "context" in result.output.lower() + + def test_view_rich_panel_with_workflows(self): + """``apm view`` Rich panel shows workflow count when present.""" + with self._chdir_tmp() as tmp: + pkg_dir = self._make_package(tmp, "wforg2", "wfrepo2") + prompts_dir = pkg_dir / ".apm" / "prompts" + prompts_dir.mkdir(parents=True) + (prompts_dir / "build.prompt.md").write_text("# Build workflow") + os.chdir(tmp) + result = self.runner.invoke(cli, ["view", "wforg2/wfrepo2"]) + assert result.exit_code == 0 + assert "workflow" in result.output.lower() or "1" in result.output + + def test_view_rich_versions_table(self): + """``apm view org/repo versions`` renders a Rich table when Rich is available.""" + mock_refs = [ + RemoteRef(name="v1.0.0", ref_type=GitReferenceType.TAG, + commit_sha="aabbccdd11223344"), + RemoteRef(name="main", ref_type=GitReferenceType.BRANCH, + commit_sha="deadbeef12345678"), + ] + with patch( + "apm_cli.commands.view.GitHubPackageDownloader" + ) as mock_cls, patch("apm_cli.commands.view.AuthResolver"): + mock_cls.return_value.list_remote_refs.return_value = mock_refs + # No _force_rich_fallback -- let Rich render + result = self.runner.invoke( + cli, ["view", "richorg/richrepo", "versions"] + ) + assert result.exit_code == 0 + assert "v1.0.0" in result.output + assert "main" in result.output + + +class TestViewLockfileRef(_InfoCmdBase): + """Tests for lockfile ref/commit display in ``apm view``.""" + + def _write_lockfile(self, project_root: Path, repo_url: str, + resolved_ref: str, resolved_commit: str) -> None: + """Write a minimal apm.lock.yaml for testing.""" + import yaml + + lockfile_data = { + "lockfile_version": "1", + "generated_at": "2026-01-01T00:00:00Z", + "dependencies": [ + { + "repo_url": repo_url, + "host": "github.com", + "resolved_ref": resolved_ref, + "resolved_commit": resolved_commit, + "depth": 0, + "resolved_by": "direct", + } + ], + } + lockfile_path = project_root / "apm.lock.yaml" + lockfile_path.write_text(yaml.safe_dump(lockfile_data), encoding="utf-8") + + def test_view_shows_lockfile_ref(self): + """``apm view`` displays resolved ref from apm.lock.yaml.""" + with self._chdir_tmp() as tmp: + self._make_package(tmp, "lockorg", "lockrepo") + self._write_lockfile( + tmp, + repo_url="https://github.com/lockorg/lockrepo", + resolved_ref="v2.5.0", + resolved_commit="abc123def456abc1", + ) + os.chdir(tmp) + with _force_rich_fallback(): + result = self.runner.invoke(cli, ["view", "lockorg/lockrepo"]) + assert result.exit_code == 0 + assert "v2.5.0" in result.output + + def test_view_shows_lockfile_commit(self): + """``apm view`` displays truncated commit SHA from apm.lock.yaml.""" + with self._chdir_tmp() as tmp: + self._make_package(tmp, "lockorg2", "lockrepo2") + self._write_lockfile( + tmp, + repo_url="https://github.com/lockorg2/lockrepo2", + resolved_ref="main", + resolved_commit="deadbeef12345678abcdef", + ) + os.chdir(tmp) + with _force_rich_fallback(): + result = self.runner.invoke(cli, ["view", "lockorg2/lockrepo2"]) + assert result.exit_code == 0 + assert "deadbeef" in result.output # first 12 chars of commit + + def test_view_no_lockfile_shows_no_ref(self): + """``apm view`` works fine when no lockfile exists.""" + with self._chdir_tmp() as tmp: + self._make_package(tmp, "nolock", "nolockpkg") + os.chdir(tmp) + with _force_rich_fallback(): + result = self.runner.invoke(cli, ["view", "nolock/nolockpkg"]) + assert result.exit_code == 0 + # No ref/commit info, but metadata is still shown + assert "nolockpkg" in result.output + + def test_view_rich_shows_lockfile_ref(self): + """``apm view`` Rich panel shows resolved ref from apm.lock.yaml.""" + with self._chdir_tmp() as tmp: + self._make_package(tmp, "richlockorg", "richlockrepo") + self._write_lockfile( + tmp, + repo_url="https://github.com/richlockorg/richlockrepo", + resolved_ref="v3.1.4", + resolved_commit="cafebabe12345678", + ) + os.chdir(tmp) + result = self.runner.invoke(cli, ["view", "richlockorg/richlockrepo"]) + assert result.exit_code == 0 + assert "v3.1.4" in result.output + + +class TestViewHooks(_InfoCmdBase): + """Tests for hook display in ``apm view``.""" + + def test_view_plain_text_shows_hooks(self): + """``apm view`` plain-text fallback shows hook count.""" + with self._chdir_tmp() as tmp: + pkg_dir = self._make_package(tmp, "hkorg", "hkrepo") + hooks_dir = pkg_dir / "hooks" + hooks_dir.mkdir() + (hooks_dir / "commit-msg.json").write_text('{"hooks": []}') + (hooks_dir / "pre-push.json").write_text('{"hooks": []}') + os.chdir(tmp) + with _force_rich_fallback(): + result = self.runner.invoke(cli, ["view", "hkorg/hkrepo"]) + assert result.exit_code == 0 + assert "hook" in result.output.lower() + assert "2" in result.output + + def test_view_plain_text_shows_context_files(self): + """``apm view`` plain-text fallback shows context file count.""" + with self._chdir_tmp() as tmp: + pkg_dir = self._make_package(tmp, "ctxorg2", "ctxrepo2") + instr_dir = pkg_dir / ".apm" / "instructions" + instr_dir.mkdir(parents=True) + (instr_dir / "guide.md").write_text("# Guide") + os.chdir(tmp) + with _force_rich_fallback(): + result = self.runner.invoke(cli, ["view", "ctxorg2/ctxrepo2"]) + assert result.exit_code == 0 + assert "instructions" in result.output.lower() or "1" in result.output + + def test_view_plain_text_shows_workflows(self): + """``apm view`` plain-text fallback shows workflow count when present.""" + with self._chdir_tmp() as tmp: + pkg_dir = self._make_package(tmp, "wforg3", "wfrepo3") + prompts_dir = pkg_dir / ".apm" / "prompts" + prompts_dir.mkdir(parents=True) + (prompts_dir / "build.prompt.md").write_text("# Build") + os.chdir(tmp) + with _force_rich_fallback(): + result = self.runner.invoke(cli, ["view", "wforg3/wfrepo3"]) + assert result.exit_code == 0 + assert "workflow" in result.output.lower() or "executable" in result.output.lower() + + +class TestViewGlobalScope(_InfoCmdBase): + """Tests for ``apm view --global`` scope handling.""" + + def test_view_global_flag_uses_user_scope(self): + """``apm view --global`` resolves packages from user scope.""" + import tempfile as _tempfile + + with _tempfile.TemporaryDirectory() as user_apm_dir: + user_apm_path = Path(user_apm_dir) + # Create a package in the simulated user APM dir + pkg_dir = user_apm_path / "apm_modules" / "globalorg" / "globalrepo" + pkg_dir.mkdir(parents=True) + (pkg_dir / "apm.yml").write_text( + "name: globalrepo\nversion: 9.9.9\n" + "description: Global pkg\nauthor: Eve\n" + ) + with patch( + "apm_cli.core.scope.get_apm_dir", + return_value=user_apm_path, + ): + with _force_rich_fallback(): + result = self.runner.invoke( + cli, ["view", "globalorg/globalrepo", "--global"] + ) + assert result.exit_code == 0 + assert "9.9.9" in result.output + assert "Global pkg" in result.output + + def test_view_global_flag_missing_apm_modules(self): + """``apm view --global`` exits with error when user scope has no apm_modules/.""" + import tempfile as _tempfile + + with _tempfile.TemporaryDirectory() as user_apm_dir: + user_apm_path = Path(user_apm_dir) + # No apm_modules/ in user scope + with patch( + "apm_cli.core.scope.get_apm_dir", + return_value=user_apm_path, + ): + result = self.runner.invoke( + cli, ["view", "missing/pkg", "--global"] + ) + assert result.exit_code == 1 + + class TestInfoAlias(_InfoCmdBase): """Verify ``apm info`` still works as a hidden backward-compatible alias."""