Skip to content

Commit cb85fb3

Browse files
committed
test: assert request construction, streaming order, status mapping, SDK contracts
- Fakes updated to real SDK contracts: McpStdioServer requires name; CapabilitiesConfig gains disabled_tools (enabled_tools defaults None, mutually exclusive); FakeApprovalMode values deny_all/auto_review; enum-based FakeBuiltinTools; FakeClaudeOptions gains cwd/mcp_servers/ resume/max_budget_usd/setting_sources/max_turns; ToolResult gains error/exception. - claude: assert permission_mode per mode, full request shape, resume, dropped_options, streaming order (deltas/tool events before completed; error status via tool_use_id), result-only single delta, max_turns and errors-list finish_reason, network rejection. - codex: assert new approval mapping (4 modes), sandbox mapping (3), thread_start/run kwargs, failed/interrupted status, tool audits from items, RootModel unwrap, usage fallback, allowed/disallowed/budget/network rejections. - antigravity: assert McpStdioServer name, disallowed->disabled, DEFAULT nondestructive, PERMISSIVE all_tools, STRICT read_only+empty policies, invalid tool + allow/deny-both + budget/network rejections, private data dir, missing-key unavailable error. - events: access_token redacted, input_tokens not. - new tests/test_sdk_contract.py: importorskip-gated pure introspection of the real surfaces this package depends on across all three SDKs.
1 parent 324bc63 commit cb85fb3

5 files changed

Lines changed: 931 additions & 111 deletions

File tree

tests/test_antigravity_adapter.py

Lines changed: 237 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22

3-
from dataclasses import dataclass
3+
import enum
4+
from dataclasses import dataclass, field
45
from pathlib import Path
56
from typing import Any
67

@@ -11,14 +12,45 @@
1112
AgentTask,
1213
FilesystemAccess,
1314
McpServerConfig,
15+
PermissionMode,
1416
PermissionProfile,
1517
)
1618
from agent_runtime_kit._errors import UnsupportedTaskInputError
1719
from agent_runtime_kit.adapters import AntigravityAgentRuntime
1820
from agent_runtime_kit.testing import RecordingEventSink
1921

2022

23+
class FakeBuiltinTools(str, enum.Enum):
24+
"""Mirror the real ``BuiltinTools`` enum values so validation behaves the same."""
25+
26+
LIST_DIR = "list_directory"
27+
VIEW_FILE = "view_file"
28+
EDIT_FILE = "edit_file"
29+
RUN_COMMAND = "run_command"
30+
START_SUBAGENT = "start_subagent"
31+
FINISH = "finish"
32+
33+
@staticmethod
34+
def read_only() -> list[FakeBuiltinTools]:
35+
return [FakeBuiltinTools.LIST_DIR, FakeBuiltinTools.VIEW_FILE, FakeBuiltinTools.FINISH]
36+
37+
@staticmethod
38+
def nondestructive() -> list[FakeBuiltinTools]:
39+
return [
40+
FakeBuiltinTools.LIST_DIR,
41+
FakeBuiltinTools.VIEW_FILE,
42+
FakeBuiltinTools.EDIT_FILE,
43+
FakeBuiltinTools.FINISH,
44+
]
45+
46+
@staticmethod
47+
def all_tools() -> list[FakeBuiltinTools]:
48+
return list(FakeBuiltinTools)
49+
50+
2151
class FakeTypes:
52+
BuiltinTools = FakeBuiltinTools
53+
2254
@dataclass
2355
class Text:
2456
text: str
@@ -37,31 +69,20 @@ class ToolResult:
3769
name: str
3870
result: str
3971
args: dict[str, Any] | None = None
72+
error: str | None = None
73+
exception: Exception | None = None
4074

4175
@dataclass
4276
class CapabilitiesConfig:
43-
enabled_tools: list[Any]
77+
enabled_tools: list[Any] | None = None
78+
disabled_tools: list[Any] | None = None
4479
enable_subagents: bool = False
4580

4681
@dataclass
4782
class McpStdioServer:
83+
name: str
4884
command: str
49-
args: list[str]
50-
51-
class BuiltinTools:
52-
START_SUBAGENT = "start_subagent"
53-
54-
@staticmethod
55-
def read_only() -> list[str]:
56-
return ["read"]
57-
58-
@staticmethod
59-
def nondestructive() -> list[str]:
60-
return ["read", "search"]
61-
62-
@staticmethod
63-
def all_tools() -> list[str]:
64-
return ["read", "write", "start_subagent"]
85+
args: list[str] = field(default_factory=list)
6586

6687

6788
class FakePolicy:
@@ -75,8 +96,26 @@ def __init__(self, **kwargs: Any) -> None:
7596
self.kwargs = kwargs
7697

7798

99+
class FakeUsage:
100+
prompt_token_count = 5
101+
candidates_token_count = 7
102+
thoughts_token_count = 2
103+
cached_content_token_count = 1
104+
total_token_count = 14
105+
106+
107+
class FakeResponse:
108+
def __init__(self, prompt: str, chunks_factory: Any) -> None:
109+
self.chunks = chunks_factory(prompt)
110+
self.usage_metadata = FakeUsage()
111+
112+
async def structured_output(self) -> dict[str, bool]:
113+
return {"ok": True}
114+
115+
78116
class FakeAgent:
79117
last_config: FakeConfig | None = None
118+
chunks_factory: Any = None
80119

81120
def __init__(self, config: FakeConfig) -> None:
82121
FakeAgent.last_config = config
@@ -89,24 +128,7 @@ async def __aexit__(self, *args: object) -> None:
89128
return None
90129

91130
async def chat(self, prompt: str) -> FakeResponse:
92-
return FakeResponse(prompt)
93-
94-
95-
class FakeUsage:
96-
prompt_token_count = 5
97-
candidates_token_count = 7
98-
thoughts_token_count = 2
99-
cached_content_token_count = 1
100-
total_token_count = 14
101-
102-
103-
class FakeResponse:
104-
def __init__(self, prompt: str) -> None:
105-
self.chunks = _chunks(prompt)
106-
self.usage_metadata = FakeUsage()
107-
108-
async def structured_output(self) -> dict[str, bool]:
109-
return {"ok": True}
131+
return FakeResponse(prompt, FakeAgent.chunks_factory or _chunks)
110132

111133

112134
async def _chunks(prompt: str):
@@ -116,17 +138,28 @@ async def _chunks(prompt: str):
116138
yield FakeTypes.ToolResult("Read", "ok", {"path": "README.md"})
117139

118140

119-
@pytest.mark.asyncio
120-
async def test_antigravity_runtime_runs_with_injected_sdk(tmp_path: Path) -> None:
121-
sink = RecordingEventSink()
122-
runtime = AntigravityAgentRuntime(
123-
api_key="key",
141+
def make_runtime(*, data_dir: Path, api_key: str = "key") -> AntigravityAgentRuntime:
142+
return AntigravityAgentRuntime(
143+
api_key=api_key,
144+
data_dir=data_dir,
124145
agent_cls=FakeAgent,
125146
config_cls=FakeConfig,
126147
types_module=FakeTypes,
127148
policy_module=FakePolicy,
128149
)
129150

151+
152+
@pytest.fixture(autouse=True)
153+
def _reset_agent() -> None:
154+
FakeAgent.last_config = None
155+
FakeAgent.chunks_factory = None
156+
157+
158+
@pytest.mark.asyncio
159+
async def test_antigravity_runtime_runs_with_injected_sdk(tmp_path: Path) -> None:
160+
sink = RecordingEventSink()
161+
runtime = make_runtime(data_dir=tmp_path)
162+
130163
result = await runtime.run(
131164
AgentTask(
132165
goal="task",
@@ -149,15 +182,141 @@ async def test_antigravity_runtime_runs_with_injected_sdk(tmp_path: Path) -> Non
149182

150183

151184
@pytest.mark.asyncio
152-
async def test_antigravity_rejects_mcp_env() -> None:
153-
runtime = AntigravityAgentRuntime(
154-
api_key="key",
155-
agent_cls=FakeAgent,
156-
config_cls=FakeConfig,
157-
types_module=FakeTypes,
158-
policy_module=FakePolicy,
185+
async def test_antigravity_mcp_server_gets_name(tmp_path: Path) -> None:
186+
runtime = make_runtime(data_dir=tmp_path)
187+
188+
await runtime.run(
189+
AgentTask(
190+
goal="task",
191+
mcp_servers=(McpServerConfig(name="fs", command="mcp", args=("--root",)),),
192+
)
159193
)
160194

195+
assert FakeAgent.last_config is not None
196+
servers = FakeAgent.last_config.kwargs["mcp_servers"]
197+
assert servers[0].name == "fs"
198+
assert servers[0].command == "mcp"
199+
200+
201+
@pytest.mark.asyncio
202+
async def test_antigravity_default_mode_uses_nondestructive(tmp_path: Path) -> None:
203+
runtime = make_runtime(data_dir=tmp_path)
204+
205+
await runtime.run(AgentTask(goal="task"))
206+
207+
capabilities = FakeAgent.last_config.kwargs["capabilities"] # type: ignore[union-attr]
208+
assert capabilities.enabled_tools == FakeBuiltinTools.nondestructive()
209+
210+
211+
@pytest.mark.asyncio
212+
async def test_antigravity_permissive_uses_all_tools(tmp_path: Path) -> None:
213+
runtime = make_runtime(data_dir=tmp_path)
214+
215+
await runtime.run(
216+
AgentTask(goal="task", permissions=PermissionProfile(mode=PermissionMode.PERMISSIVE))
217+
)
218+
219+
config = FakeAgent.last_config
220+
assert config is not None
221+
assert config.kwargs["capabilities"].enabled_tools == FakeBuiltinTools.all_tools()
222+
assert config.kwargs["capabilities"].enable_subagents is True
223+
assert config.kwargs["policies"] == ["allow_all"]
224+
225+
226+
@pytest.mark.asyncio
227+
async def test_antigravity_strict_uses_read_only_and_no_policies(tmp_path: Path) -> None:
228+
runtime = make_runtime(data_dir=tmp_path)
229+
230+
await runtime.run(
231+
AgentTask(goal="task", permissions=PermissionProfile(mode=PermissionMode.STRICT))
232+
)
233+
234+
config = FakeAgent.last_config
235+
assert config is not None
236+
assert config.kwargs["capabilities"].enabled_tools == FakeBuiltinTools.read_only()
237+
assert config.kwargs["policies"] == []
238+
239+
240+
@pytest.mark.asyncio
241+
async def test_antigravity_disallowed_tools_map_to_disabled(tmp_path: Path) -> None:
242+
runtime = make_runtime(data_dir=tmp_path)
243+
244+
await runtime.run(
245+
AgentTask(
246+
goal="task",
247+
permissions=PermissionProfile(disallowed_tools=("run_command",)),
248+
)
249+
)
250+
251+
config = FakeAgent.last_config
252+
assert config is not None
253+
capabilities = config.kwargs["capabilities"]
254+
assert capabilities.disabled_tools == ["run_command"]
255+
# enabled_tools and disabled_tools are mutually exclusive in the real SDK.
256+
assert capabilities.enabled_tools is None
257+
258+
259+
@pytest.mark.asyncio
260+
async def test_antigravity_rejects_allow_and_deny_list_together(tmp_path: Path) -> None:
261+
runtime = make_runtime(data_dir=tmp_path)
262+
263+
with pytest.raises(UnsupportedTaskInputError):
264+
await runtime.run(
265+
AgentTask(
266+
goal="task",
267+
permissions=PermissionProfile(
268+
allowed_tools=("view_file",), disallowed_tools=("run_command",)
269+
),
270+
)
271+
)
272+
273+
274+
@pytest.mark.asyncio
275+
async def test_antigravity_invalid_allowed_tool_raises(tmp_path: Path) -> None:
276+
runtime = make_runtime(data_dir=tmp_path)
277+
278+
with pytest.raises(UnsupportedTaskInputError):
279+
await runtime.run(
280+
AgentTask(goal="task", permissions=PermissionProfile(allowed_tools=("Read",)))
281+
)
282+
283+
284+
@pytest.mark.asyncio
285+
async def test_antigravity_tool_result_error_status(tmp_path: Path) -> None:
286+
async def error_chunks(prompt: str):
287+
yield FakeTypes.Text("partial")
288+
yield FakeTypes.ToolResult("Edit", "", {"p": "x"}, error="disk full")
289+
290+
FakeAgent.chunks_factory = error_chunks
291+
runtime = make_runtime(data_dir=tmp_path)
292+
293+
result = await runtime.run(AgentTask(goal="task"))
294+
295+
assert result.tool_calls[0].status == "error"
296+
297+
298+
@pytest.mark.asyncio
299+
async def test_antigravity_rejects_budget(tmp_path: Path) -> None:
300+
runtime = make_runtime(data_dir=tmp_path)
301+
302+
with pytest.raises(UnsupportedTaskInputError):
303+
await runtime.run(AgentTask(goal="task", budget_usd=1.0))
304+
305+
306+
@pytest.mark.asyncio
307+
async def test_antigravity_rejects_network(tmp_path: Path) -> None:
308+
runtime = make_runtime(data_dir=tmp_path)
309+
310+
with pytest.raises(UnsupportedTaskInputError):
311+
await runtime.run(
312+
AgentTask(goal="task", permissions=PermissionProfile(network=True))
313+
)
314+
315+
316+
@pytest.mark.asyncio
317+
async def test_antigravity_rejects_mcp_env(tmp_path: Path) -> None:
318+
runtime = make_runtime(data_dir=tmp_path)
319+
161320
with pytest.raises(UnsupportedTaskInputError):
162321
await runtime.run(
163322
AgentTask(
@@ -167,15 +326,43 @@ async def test_antigravity_rejects_mcp_env() -> None:
167326
)
168327

169328

170-
def test_antigravity_availability_uses_injected_sdk() -> None:
329+
@pytest.mark.asyncio
330+
async def test_antigravity_missing_api_key_raises_unavailable(
331+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
332+
) -> None:
333+
from agent_runtime_kit._errors import AgentRuntimeUnavailableError
334+
335+
monkeypatch.delenv("GEMINI_API_KEY", raising=False)
336+
monkeypatch.delenv("GOOGLE_API_KEY", raising=False)
171337
runtime = AntigravityAgentRuntime(
172-
api_key="key",
338+
api_key=None,
339+
data_dir=tmp_path,
173340
agent_cls=FakeAgent,
174341
config_cls=FakeConfig,
175342
types_module=FakeTypes,
176343
policy_module=FakePolicy,
177344
)
178345

346+
with pytest.raises(AgentRuntimeUnavailableError):
347+
await runtime.run(AgentTask(goal="task"))
348+
349+
350+
def test_antigravity_data_dir_is_private(tmp_path: Path) -> None:
351+
import os
352+
import stat
353+
354+
runtime = make_runtime(data_dir=tmp_path)
355+
356+
path = runtime._runtime_dir("antigravity-sessions")
357+
358+
assert path.parent == tmp_path
359+
mode = stat.S_IMODE(os.stat(path).st_mode)
360+
assert mode == 0o700
361+
362+
363+
def test_antigravity_availability_uses_injected_sdk(tmp_path: Path) -> None:
364+
runtime = make_runtime(data_dir=tmp_path)
365+
179366
diagnostic = runtime.availability()
180367

181368
assert diagnostic.kind is AgentRuntimeKind.ANTIGRAVITY_AGENT_SDK

0 commit comments

Comments
 (0)