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
20 changes: 19 additions & 1 deletion tests/unit/marketplace/test_marketplace_client.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
"""Tests for marketplace client -- HTTP mock, caching, TTL, auth, auto-detection, proxy."""

import json
import re
import time
from unittest.mock import MagicMock, patch
from urllib.parse import urlparse

import pytest

Expand All @@ -11,6 +13,22 @@
from apm_cli.marketplace.models import MarketplaceSource


def _quoted_hosts(text: str) -> set[str]:
"""Extract host tokens from `Host '<host>'` patterns in error text.

Each token is normalised through ``urllib.parse.urlparse`` so callers
compare on parsed hostnames (set equality), not raw substrings -- which
is what CodeQL's ``py/incomplete-url-substring-sanitization`` rule
requires (see ``.github/instructions/tests.instructions.md``).
"""
hosts: set[str] = set()
for m in re.finditer(r"Host '([^']+)'", text, re.IGNORECASE):
parsed = urlparse(f"https://{m.group(1)}")
if parsed.hostname:
hosts.add(parsed.hostname)
return hosts


@pytest.fixture(autouse=True)
def _isolate_cache(tmp_path, monkeypatch):
"""Point cache and config to temp directories."""
Expand Down Expand Up @@ -494,7 +512,7 @@ def test_generic_host_rejected_before_request(self):

# No HTTP request should have been issued (no credential leakage).
mock_get.assert_not_called()
assert "gitlab.com" in str(excinfo.value)
assert _quoted_hosts(str(excinfo.value)) == {"gitlab.com"}
assert "not a supported marketplace source" in str(excinfo.value)

def test_github_host_passes_guard(self):
Expand Down
24 changes: 21 additions & 3 deletions tests/unit/marketplace/test_marketplace_commands.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
"""Tests for marketplace CLI commands using CliRunner."""

import json # noqa: F401
import re
from unittest.mock import MagicMock, patch # noqa: F401
from urllib.parse import urlparse

import pytest
from click.testing import CliRunner
Expand All @@ -13,6 +15,22 @@
)


def _quoted_hosts(text: str) -> set[str]:
"""Extract host tokens from `Host '<host>'` patterns in error text.

Each token is normalised through ``urllib.parse.urlparse`` so callers
compare on parsed hostnames (set equality), not raw substrings -- which
is what CodeQL's ``py/incomplete-url-substring-sanitization`` rule
requires (see ``.github/instructions/tests.instructions.md``).
"""
hosts: set[str] = set()
for m in re.finditer(r"Host '([^']+)'", text, re.IGNORECASE):
parsed = urlparse(f"https://{m.group(1)}")
if parsed.hostname:
hosts.add(parsed.hostname)
return hosts


@pytest.fixture
def runner():
return CliRunner()
Expand Down Expand Up @@ -297,15 +315,15 @@ def test_add_rejects_non_github_host_with_actionable_error(self, runner):
marketplace, ["add", "https://gitlab.com/acme/team/plugin-marketplace"]
)
assert result.exit_code != 0
assert "gitlab.com" in result.output
assert _quoted_hosts(result.output) == {"gitlab.com"}
assert "not supported" in result.output.lower()

def test_add_rejects_non_github_host_shorthand(self, runner):
from apm_cli.commands.marketplace import marketplace

result = runner.invoke(marketplace, ["add", "gitlab.com/acme/team/plugin-marketplace"])
assert result.exit_code != 0
assert "gitlab.com" in result.output
assert _quoted_hosts(result.output) == {"gitlab.com"}
assert "not supported" in result.output.lower()

def test_add_rejects_http_url(self, runner):
Expand Down Expand Up @@ -399,7 +417,7 @@ def test_untrusted_host_error_has_action_in_first_sentence(self, runner):
# leak", etc.) must NOT appear in the default error path.
first_line = next((line for line in result.output.splitlines() if line.strip()), "").lower()
assert "not supported" in first_line
assert "gitlab.com" in first_line
assert _quoted_hosts(first_line) == {"gitlab.com"}
assert "credential" not in result.output.lower()
assert "leak" not in result.output.lower()

Expand Down
Loading