Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions src/apm_cli/commands/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,8 +247,21 @@ def _validate_package_exists(package, verbose=False):

# For virtual packages, use the downloader's validation method
if dep_ref.is_virtual:
downloader = GitHubPackageDownloader()
return downloader.validate_virtual_package_exists(dep_ref)
ctx = auth_resolver.resolve_for_dep(dep_ref)
host = dep_ref.host or default_host()
org = dep_ref.repo_url.split('/')[0] if dep_ref.repo_url and '/' in dep_ref.repo_url else None
if verbose_log:
verbose_log(f"Auth resolved: host={host}, org={org}, source={ctx.source}, type={ctx.token_type}")
Comment thread
danielmeppiel marked this conversation as resolved.
downloader = GitHubPackageDownloader(auth_resolver=auth_resolver)
result = downloader.validate_virtual_package_exists(dep_ref)
if not result and verbose_log:
try:
err_ctx = auth_resolver.build_error_context(host, f"accessing {package}", org=org)
for line in err_ctx.splitlines():
verbose_log(line)
except Exception:
pass
Comment thread
danielmeppiel marked this conversation as resolved.
return result

# For Azure DevOps or GitHub Enterprise (non-github.com hosts),
# use the downloader which handles authentication properly
Expand Down
41 changes: 41 additions & 0 deletions tests/unit/test_install_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,47 @@ def test_verbose_validation_failure_calls_build_error_context(self, mock_urlopen
assert call_args[0][0] == "github.com" # host
assert call_args[0][1].endswith("owner/repo") # operation

def test_verbose_virtual_package_validation_shows_auth_diagnostics(self):
"""When virtual package validation fails in verbose mode, auth diagnostics are shown."""
from apm_cli.commands.install import _validate_package_exists

with patch(
"apm_cli.deps.github_downloader.GitHubPackageDownloader.validate_virtual_package_exists",
return_value=False,
), patch.object(
__import__("apm_cli.core.auth", fromlist=["AuthResolver"]).AuthResolver,
"resolve_for_dep",
return_value=MagicMock(source="none", token_type="none", token=None),
) as mock_resolve, patch.object(
__import__("apm_cli.core.auth", fromlist=["AuthResolver"]).AuthResolver,
"build_error_context",
return_value="Authentication failed for accessing owner/repo/skills/my-skill on github.com.\nNo token available.",
) as mock_build_ctx:
result = _validate_package_exists("owner/repo/skills/my-skill", verbose=True)
assert result is False
mock_resolve.assert_called_once()
mock_build_ctx.assert_called_once()
call_args = mock_build_ctx.call_args
assert call_args[0][0] == "github.com" # host
assert "owner/repo/skills/my-skill" in call_args[0][1] # operation

Comment thread
danielmeppiel marked this conversation as resolved.
def test_virtual_package_validation_reuses_auth_resolver(self):
"""Virtual package validation should pass its AuthResolver to the downloader."""
from apm_cli.commands.install import _validate_package_exists

with patch(
"apm_cli.deps.github_downloader.GitHubPackageDownloader.__init__",
return_value=None,
) as mock_init, patch(
"apm_cli.deps.github_downloader.GitHubPackageDownloader.validate_virtual_package_exists",
return_value=True,
):
_validate_package_exists("owner/repo/skills/my-skill", verbose=False)
mock_init.assert_called_once()
# The auth_resolver kwarg should be passed (not creating a new one)
_, kwargs = mock_init.call_args
assert "auth_resolver" in kwargs


# ---------------------------------------------------------------------------
# Transitive dep parent chain breadcrumb
Expand Down
Loading