|
| 1 | +"""Tests for scripts/test_native_tools_sandbox.py.""" |
| 2 | + |
| 3 | +from __future__ import annotations |
| 4 | + |
| 5 | +import importlib.util |
| 6 | +from pathlib import Path |
| 7 | +from types import SimpleNamespace |
| 8 | + |
| 9 | +import pytest |
| 10 | + |
| 11 | +MODULE_PATH = Path(__file__).resolve().parents[2] / "scripts" / "test_native_tools_sandbox.py" |
| 12 | +SPEC = importlib.util.spec_from_file_location("test_native_tools_sandbox_script_module", MODULE_PATH) |
| 13 | +assert SPEC and SPEC.loader |
| 14 | +SANDBOX_SCRIPT = importlib.util.module_from_spec(SPEC) |
| 15 | +SPEC.loader.exec_module(SANDBOX_SCRIPT) |
| 16 | + |
| 17 | + |
| 18 | +@pytest.mark.parametrize( |
| 19 | + ("backend_type", "requested_runner", "expected_runner"), |
| 20 | + [ |
| 21 | + ("claude_code", "auto", "direct"), |
| 22 | + ("codex", "auto", "direct"), |
| 23 | + ("copilot", "auto", "orchestrator"), |
| 24 | + ("gemini_cli", "auto", "orchestrator"), |
| 25 | + ("copilot", "direct", "direct"), |
| 26 | + ("gemini_cli", "orchestrator", "orchestrator"), |
| 27 | + ], |
| 28 | +) |
| 29 | +def test_resolve_runner(backend_type: str, requested_runner: str, expected_runner: str) -> None: |
| 30 | + """Auto mode should choose the realistic runner per backend.""" |
| 31 | + assert SANDBOX_SCRIPT.resolve_runner(backend_type, requested_runner) == expected_runner |
| 32 | + |
| 33 | + |
| 34 | +def test_build_orchestrator_runner_uses_single_agent_quick_mode(monkeypatch: pytest.MonkeyPatch) -> None: |
| 35 | + """Orchestrator runner should use a single-agent, presentation-free config.""" |
| 36 | + captured: dict[str, object] = {} |
| 37 | + backend = SimpleNamespace(model="test-model") |
| 38 | + |
| 39 | + def fake_configurable_agent(*, config, backend): |
| 40 | + captured["agent_config"] = config |
| 41 | + captured["agent_backend"] = backend |
| 42 | + return SimpleNamespace(agent_id=config.agent_id, backend=backend) |
| 43 | + |
| 44 | + def fake_create_orchestrator(agents, **kwargs): |
| 45 | + captured["agents"] = agents |
| 46 | + captured["orchestrator_config"] = kwargs.get("config") |
| 47 | + return "fake-orchestrator" |
| 48 | + |
| 49 | + monkeypatch.setattr(SANDBOX_SCRIPT, "ConfigurableAgent", fake_configurable_agent) |
| 50 | + monkeypatch.setattr(SANDBOX_SCRIPT, "create_orchestrator", fake_create_orchestrator) |
| 51 | + |
| 52 | + orchestrator = SANDBOX_SCRIPT.build_orchestrator_runner(backend) |
| 53 | + |
| 54 | + assert orchestrator == "fake-orchestrator" |
| 55 | + assert captured["agent_backend"] is backend |
| 56 | + assert captured["agents"] == [("sandbox_agent", SimpleNamespace(agent_id="sandbox_agent", backend=backend))] |
| 57 | + |
| 58 | + agent_config = captured["agent_config"] |
| 59 | + assert agent_config.agent_id == "sandbox_agent" |
| 60 | + assert agent_config.backend_params["model"] == "test-model" |
| 61 | + |
| 62 | + orchestrator_config = captured["orchestrator_config"] |
| 63 | + assert orchestrator_config.skip_voting is True |
| 64 | + assert orchestrator_config.skip_final_presentation is True |
| 65 | + assert orchestrator_config.max_new_answers_per_agent == 1 |
| 66 | + assert orchestrator_config.final_answer_strategy == "winner_reuse" |
| 67 | + assert orchestrator_config.coordination_config.write_mode == "legacy" |
| 68 | + |
| 69 | + |
| 70 | +@pytest.mark.asyncio |
| 71 | +async def test_run_agent_task_dispatches_to_orchestrator_runner(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: |
| 72 | + """SandboxTester should route execution through the orchestrator helper when selected.""" |
| 73 | + workspace = tmp_path / "workspace" |
| 74 | + writable = tmp_path / "writable" |
| 75 | + readonly = tmp_path / "readonly" |
| 76 | + outside = tmp_path / "outside" |
| 77 | + for path in (workspace, writable, readonly, outside): |
| 78 | + path.mkdir() |
| 79 | + |
| 80 | + tester = SANDBOX_SCRIPT.SandboxTester( |
| 81 | + workspace, |
| 82 | + writable, |
| 83 | + readonly, |
| 84 | + outside, |
| 85 | + tmp_path, |
| 86 | + runner_type="orchestrator", |
| 87 | + ) |
| 88 | + |
| 89 | + fake_backend = object() |
| 90 | + |
| 91 | + monkeypatch.setattr(tester, "create_backend", lambda: fake_backend) |
| 92 | + |
| 93 | + async def fake_run_with_orchestrator(prompt, backend): |
| 94 | + assert prompt == "Read something" |
| 95 | + assert backend is fake_backend |
| 96 | + return "orchestrated-response" |
| 97 | + |
| 98 | + async def fake_run_with_direct_backend(prompt, backend): |
| 99 | + raise AssertionError("direct backend path should not be used") |
| 100 | + |
| 101 | + monkeypatch.setattr(tester, "_run_with_orchestrator", fake_run_with_orchestrator) |
| 102 | + monkeypatch.setattr(tester, "_run_with_direct_backend", fake_run_with_direct_backend) |
| 103 | + |
| 104 | + response = await tester.run_agent_task("Read something") |
| 105 | + |
| 106 | + assert response == "orchestrated-response" |
0 commit comments