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
6 changes: 6 additions & 0 deletions tests/integration/test_context_optimizer_phase3w4.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,17 @@


def _make_instruction(apply_to: str | None = "**/*.py", content: str = "content") -> Instruction:
from pathlib import Path as _Path

inst = MagicMock(spec=Instruction)
inst.apply_to = apply_to
inst.content = content
inst.source_file = None
inst.source = None
# Production fallback path reads instruction.file_path.stem when an
# instruction matches no files; spec=Instruction does not expose
# dataclass fields automatically, so set it explicitly.
inst.file_path = _Path("test.instructions.md")
return inst


Expand Down
6 changes: 6 additions & 0 deletions tests/integration/test_context_optimizer_placement.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,17 @@


def _make_instruction(apply_to: str | None = "**/*.py", content: str = "content") -> Instruction:
from pathlib import Path as _Path

inst = MagicMock(spec=Instruction)
inst.apply_to = apply_to
inst.content = content
inst.source_file = None
inst.source = None
# Production fallback path reads instruction.file_path.stem when an
# instruction matches no files; spec=Instruction does not expose
# dataclass fields automatically, so set it explicitly.
inst.file_path = _Path("test.instructions.md")
return inst


Expand Down
30 changes: 18 additions & 12 deletions tests/integration/test_git_cache_hermetic.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ class TestGetCheckout:
def test_cache_hit_returns_existing_checkout(self, cache: GitCache) -> None:
url = "https://github.com/owner/repo.git"
sha = "a" * 40
checkout_dir = cache._checkouts_root / cache_shard_key(url) / sha
checkout_dir = cache._checkouts_root / cache_shard_key(url) / sha / "full"
checkout_dir.mkdir(parents=True)

with (
Expand All @@ -93,7 +93,7 @@ def test_cache_hit_returns_existing_checkout(self, cache: GitCache) -> None:
def test_cache_hit_with_failed_integrity_evicts_and_recreates(self, cache: GitCache) -> None:
url = "https://github.com/owner/repo.git"
sha = "b" * 40
checkout_dir = cache._checkouts_root / cache_shard_key(url) / sha
checkout_dir = cache._checkouts_root / cache_shard_key(url) / sha / "full"
checkout_dir.mkdir(parents=True)
recreated = checkout_dir.parent / "recreated"

Expand All @@ -108,8 +108,10 @@ def test_cache_hit_with_failed_integrity_evicts_and_recreates(self, cache: GitCa

assert result == recreated
mock_evict.assert_called_once_with(checkout_dir)
mock_ensure.assert_called_once_with(url, cache_shard_key(url), sha, env=None)
mock_create.assert_called_once_with(url, cache_shard_key(url), sha, env=None)
mock_ensure.assert_called_once_with(url, cache_shard_key(url), sha, env=None, partial=False)
mock_create.assert_called_once_with(
url, cache_shard_key(url), sha, env=None, sparse_paths=None, promisor_url=None
)

def test_refresh_ignores_existing_checkout(self, tmp_path: Path) -> None:
with (
Expand All @@ -119,7 +121,7 @@ def test_refresh_ignores_existing_checkout(self, tmp_path: Path) -> None:
cache = GitCache(tmp_path, refresh=True)
url = "https://github.com/owner/repo.git"
sha = "c" * 40
checkout_dir = cache._checkouts_root / cache_shard_key(url) / sha
checkout_dir = cache._checkouts_root / cache_shard_key(url) / sha / "full"
checkout_dir.mkdir(parents=True)

with (
Expand All @@ -138,7 +140,7 @@ def test_refresh_ignores_existing_checkout(self, tmp_path: Path) -> None:
def test_cache_miss_creates_checkout(self, cache: GitCache) -> None:
url = "https://github.com/owner/repo.git"
sha = "d" * 40
checkout_dir = cache._checkouts_root / cache_shard_key(url) / sha
checkout_dir = cache._checkouts_root / cache_shard_key(url) / sha / "full"

with (
patch.object(cache, "_resolve_sha", return_value=sha),
Expand All @@ -148,8 +150,12 @@ def test_cache_miss_creates_checkout(self, cache: GitCache) -> None:
result = cache.get_checkout(url, None, locked_sha=sha, env={"A": "1"})

assert result == checkout_dir
mock_ensure.assert_called_once_with(url, cache_shard_key(url), sha, env={"A": "1"})
mock_create.assert_called_once_with(url, cache_shard_key(url), sha, env={"A": "1"})
mock_ensure.assert_called_once_with(
url, cache_shard_key(url), sha, env={"A": "1"}, partial=False
)
mock_create.assert_called_once_with(
url, cache_shard_key(url), sha, env={"A": "1"}, sparse_paths=None, promisor_url=None
)


class TestResolveSha:
Expand Down Expand Up @@ -447,7 +453,7 @@ class TestCreateCheckout:
def test_write_dedup_hit_under_lock_returns_existing_checkout(self, cache: GitCache) -> None:
url = "https://example.com/repo.git"
shard_key = cache_shard_key(url)
final_dir = cache._checkouts_root / shard_key / ("a" * 40)
final_dir = cache._checkouts_root / shard_key / ("a" * 40) / "full"
final_dir.mkdir(parents=True)

with (
Expand All @@ -465,7 +471,7 @@ def test_invalid_existing_checkout_recreates_from_bare_repo(self, cache: GitCach
shard_key = cache_shard_key(url)
bare_dir = cache._db_root / shard_key
bare_dir.mkdir(parents=True)
final_dir = cache._checkouts_root / shard_key / ("b" * 40)
final_dir = cache._checkouts_root / shard_key / ("b" * 40) / "full"
final_dir.mkdir(parents=True)

def _land(staged: Path, final: Path, _lock: object) -> bool:
Expand Down Expand Up @@ -534,7 +540,7 @@ def test_checkout_failure_cleans_staged_checkout(self, cache: GitCache) -> None:
def test_atomic_land_false_accepts_valid_winner(self, cache: GitCache) -> None:
url = "https://example.com/repo.git"
shard_key = cache_shard_key(url)
final_dir = cache._checkouts_root / shard_key / ("e" * 40)
final_dir = cache._checkouts_root / shard_key / ("e" * 40) / "full"
final_dir.mkdir(parents=True)
(cache._db_root / shard_key).mkdir(parents=True)

Expand All @@ -556,7 +562,7 @@ def test_atomic_land_false_accepts_valid_winner(self, cache: GitCache) -> None:
def test_atomic_land_false_with_invalid_winner_evicts_and_raises(self, cache: GitCache) -> None:
url = "https://example.com/repo.git"
shard_key = cache_shard_key(url)
final_dir = cache._checkouts_root / shard_key / ("f" * 40)
final_dir = cache._checkouts_root / shard_key / ("f" * 40) / "full"
final_dir.mkdir(parents=True)
(cache._db_root / shard_key).mkdir(parents=True)

Expand Down
30 changes: 18 additions & 12 deletions tests/integration/test_git_cache_phase3w5.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ class TestGetCheckout:
def test_cache_hit_returns_existing_checkout(self, cache: GitCache) -> None:
url = "https://github.com/owner/repo.git"
sha = "a" * 40
checkout_dir = cache._checkouts_root / cache_shard_key(url) / sha
checkout_dir = cache._checkouts_root / cache_shard_key(url) / sha / "full"
checkout_dir.mkdir(parents=True)

with (
Expand All @@ -93,7 +93,7 @@ def test_cache_hit_returns_existing_checkout(self, cache: GitCache) -> None:
def test_cache_hit_with_failed_integrity_evicts_and_recreates(self, cache: GitCache) -> None:
url = "https://github.com/owner/repo.git"
sha = "b" * 40
checkout_dir = cache._checkouts_root / cache_shard_key(url) / sha
checkout_dir = cache._checkouts_root / cache_shard_key(url) / sha / "full"
checkout_dir.mkdir(parents=True)
recreated = checkout_dir.parent / "recreated"

Expand All @@ -108,8 +108,10 @@ def test_cache_hit_with_failed_integrity_evicts_and_recreates(self, cache: GitCa

assert result == recreated
mock_evict.assert_called_once_with(checkout_dir)
mock_ensure.assert_called_once_with(url, cache_shard_key(url), sha, env=None)
mock_create.assert_called_once_with(url, cache_shard_key(url), sha, env=None)
mock_ensure.assert_called_once_with(url, cache_shard_key(url), sha, env=None, partial=False)
mock_create.assert_called_once_with(
url, cache_shard_key(url), sha, env=None, sparse_paths=None, promisor_url=None
)

def test_refresh_ignores_existing_checkout(self, tmp_path: Path) -> None:
with (
Expand All @@ -119,7 +121,7 @@ def test_refresh_ignores_existing_checkout(self, tmp_path: Path) -> None:
cache = GitCache(tmp_path, refresh=True)
url = "https://github.com/owner/repo.git"
sha = "c" * 40
checkout_dir = cache._checkouts_root / cache_shard_key(url) / sha
checkout_dir = cache._checkouts_root / cache_shard_key(url) / sha / "full"
checkout_dir.mkdir(parents=True)

with (
Expand All @@ -138,7 +140,7 @@ def test_refresh_ignores_existing_checkout(self, tmp_path: Path) -> None:
def test_cache_miss_creates_checkout(self, cache: GitCache) -> None:
url = "https://github.com/owner/repo.git"
sha = "d" * 40
checkout_dir = cache._checkouts_root / cache_shard_key(url) / sha
checkout_dir = cache._checkouts_root / cache_shard_key(url) / sha / "full"

with (
patch.object(cache, "_resolve_sha", return_value=sha),
Expand All @@ -148,8 +150,12 @@ def test_cache_miss_creates_checkout(self, cache: GitCache) -> None:
result = cache.get_checkout(url, None, locked_sha=sha, env={"A": "1"})

assert result == checkout_dir
mock_ensure.assert_called_once_with(url, cache_shard_key(url), sha, env={"A": "1"})
mock_create.assert_called_once_with(url, cache_shard_key(url), sha, env={"A": "1"})
mock_ensure.assert_called_once_with(
url, cache_shard_key(url), sha, env={"A": "1"}, partial=False
)
mock_create.assert_called_once_with(
url, cache_shard_key(url), sha, env={"A": "1"}, sparse_paths=None, promisor_url=None
)


class TestResolveSha:
Expand Down Expand Up @@ -447,7 +453,7 @@ class TestCreateCheckout:
def test_write_dedup_hit_under_lock_returns_existing_checkout(self, cache: GitCache) -> None:
url = "https://example.com/repo.git"
shard_key = cache_shard_key(url)
final_dir = cache._checkouts_root / shard_key / ("a" * 40)
final_dir = cache._checkouts_root / shard_key / ("a" * 40) / "full"
final_dir.mkdir(parents=True)

with (
Expand All @@ -465,7 +471,7 @@ def test_invalid_existing_checkout_recreates_from_bare_repo(self, cache: GitCach
shard_key = cache_shard_key(url)
bare_dir = cache._db_root / shard_key
bare_dir.mkdir(parents=True)
final_dir = cache._checkouts_root / shard_key / ("b" * 40)
final_dir = cache._checkouts_root / shard_key / ("b" * 40) / "full"
final_dir.mkdir(parents=True)

def _land(staged: Path, final: Path, _lock: object) -> bool:
Expand Down Expand Up @@ -534,7 +540,7 @@ def test_checkout_failure_cleans_staged_checkout(self, cache: GitCache) -> None:
def test_atomic_land_false_accepts_valid_winner(self, cache: GitCache) -> None:
url = "https://example.com/repo.git"
shard_key = cache_shard_key(url)
final_dir = cache._checkouts_root / shard_key / ("e" * 40)
final_dir = cache._checkouts_root / shard_key / ("e" * 40) / "full"
final_dir.mkdir(parents=True)
(cache._db_root / shard_key).mkdir(parents=True)

Expand All @@ -556,7 +562,7 @@ def test_atomic_land_false_accepts_valid_winner(self, cache: GitCache) -> None:
def test_atomic_land_false_with_invalid_winner_evicts_and_raises(self, cache: GitCache) -> None:
url = "https://example.com/repo.git"
shard_key = cache_shard_key(url)
final_dir = cache._checkouts_root / shard_key / ("f" * 40)
final_dir = cache._checkouts_root / shard_key / ("f" * 40) / "full"
final_dir.mkdir(parents=True)
(cache._db_root / shard_key).mkdir(parents=True)

Expand Down
16 changes: 13 additions & 3 deletions tests/integration/test_mcp_integrator_characterisation.py
Original file line number Diff line number Diff line change
Expand Up @@ -297,13 +297,23 @@ def test_skips_parse_errors_and_continues(self, tmp_path: Path) -> None:
pkg.name = "good"
pkg.get_mcp_dependencies.return_value = [dep]

# Side-effect order must follow the filesystem-discovery order of
# apm.yml files (which Path.iterdir does not guarantee to be
# alphabetic across OSes/filesystems). Determine the order
# lazily so the bad-then-good (ValueError-then-pkg) sequence
# always lines up with whichever directory is enumerated first.
def _from_apm_yml(path, *_args, **_kw):
if path == first:
raise ValueError("bad")
return pkg

Comment on lines +305 to +309
with patch("apm_cli.models.apm_package.APMPackage") as mock_pkg_cls:
mock_pkg_cls.from_apm_yml.side_effect = [ValueError("bad"), pkg]
mock_pkg_cls.from_apm_yml.side_effect = _from_apm_yml
result = MCPIntegrator.collect_transitive(apm_modules)

assert result == [dep]
assert mock_pkg_cls.from_apm_yml.call_args_list[0].args[0] == first
assert mock_pkg_cls.from_apm_yml.call_args_list[1].args[0] == second
called_paths = {call.args[0] for call in mock_pkg_cls.from_apm_yml.call_args_list}
assert called_paths == {first, second}

def test_supports_lock_entries_with_virtual_path(self, tmp_path: Path) -> None:
apm_modules = tmp_path / "apm_modules"
Expand Down
6 changes: 5 additions & 1 deletion tests/integration/test_mcp_integrator_install_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,14 +329,18 @@ def test_version_overlay_warns(self):
assert any("version" in str(w.message) for w in caught)

def test_registry_overlay_warns_when_string(self):
# Historical behavior: a string `registry` field on an MCPDependency
# emitted a runtime warning. The warning was removed when the field
# became a no-op overlay (kept for forward-compat with newer apm.yml
# schemas that may attach it). Assert the silent-noop contract.
dep = _make_dep("srv")
dep.version = None
dep.registry = "custom-registry"
cache = {"srv": {"name": "srv"}}
with warnings.catch_warnings(record=True) as caught:
warnings.simplefilter("always")
MCPIntegrator._apply_overlay(cache, dep)
assert any("registry" in str(w.message) for w in caught)
assert not any("registry" in str(w.message) for w in caught)

def test_unknown_server_is_noop(self):
dep = _make_dep("missing-srv", transport="stdio")
Expand Down
6 changes: 5 additions & 1 deletion tests/integration/test_mcp_integrator_phase3w4.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,14 +329,18 @@ def test_version_overlay_warns(self):
assert any("version" in str(w.message) for w in caught)

def test_registry_overlay_warns_when_string(self):
# Historical behavior: a string `registry` field on an MCPDependency
# emitted a runtime warning. The warning was removed when the field
# became a no-op overlay (kept for forward-compat with newer apm.yml
# schemas that may attach it). Assert the silent-noop contract.
dep = _make_dep("srv")
dep.version = None
dep.registry = "custom-registry"
cache = {"srv": {"name": "srv"}}
with warnings.catch_warnings(record=True) as caught:
warnings.simplefilter("always")
MCPIntegrator._apply_overlay(cache, dep)
assert any("registry" in str(w.message) for w in caught)
assert not any("registry" in str(w.message) for w in caught)

def test_unknown_server_is_noop(self):
dep = _make_dep("missing-srv", transport="stdio")
Expand Down
16 changes: 13 additions & 3 deletions tests/integration/test_mcp_integrator_phase3w5.py
Original file line number Diff line number Diff line change
Expand Up @@ -297,13 +297,23 @@ def test_skips_parse_errors_and_continues(self, tmp_path: Path) -> None:
pkg.name = "good"
pkg.get_mcp_dependencies.return_value = [dep]

# Side-effect order must follow the filesystem-discovery order of
# apm.yml files (which Path.iterdir does not guarantee to be
# alphabetic across OSes/filesystems). Determine the order
# lazily so the bad-then-good (ValueError-then-pkg) sequence
# always lines up with whichever directory is enumerated first.
def _from_apm_yml(path, *_args, **_kw):
if path == first:
raise ValueError("bad")
return pkg

Comment on lines +305 to +309
with patch("apm_cli.models.apm_package.APMPackage") as mock_pkg_cls:
mock_pkg_cls.from_apm_yml.side_effect = [ValueError("bad"), pkg]
mock_pkg_cls.from_apm_yml.side_effect = _from_apm_yml
result = MCPIntegrator.collect_transitive(apm_modules)

assert result == [dep]
assert mock_pkg_cls.from_apm_yml.call_args_list[0].args[0] == first
assert mock_pkg_cls.from_apm_yml.call_args_list[1].args[0] == second
called_paths = {call.args[0] for call in mock_pkg_cls.from_apm_yml.call_args_list}
assert called_paths == {first, second}

def test_supports_lock_entries_with_virtual_path(self, tmp_path: Path) -> None:
apm_modules = tmp_path / "apm_modules"
Expand Down
7 changes: 6 additions & 1 deletion tests/integration/test_wave8_codex_download_coverage.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,12 +174,17 @@ def test_server_name_override(self, tmp_path: Path) -> None:
assert "custom" in data["mcp_servers"]

def test_remote_only_rejected(self, tmp_path: Path) -> None:
# Codex now accepts remote-only streamable-http servers (see
# CodexClientAdapter._format_server_config). Only SSE and
# non-https / empty-URL remotes are rejected. Assert the SSE
# rejection branch which is the actual remote-only "reject"
# contract.
from apm_cli.adapters.client.codex import CodexClientAdapter

adapter = CodexClientAdapter(project_root=tmp_path)
server_info = {
"name": "remote",
"remotes": [{"url": "https://example.com/sse"}],
"remotes": [{"url": "https://example.com/sse", "transport_type": "sse"}],
"packages": [],
}
with patch.object(adapter, "_fetch_server_info", return_value=server_info):
Expand Down
Loading