From 800aa1d6babaa28ddf0498d3af1210b90dfeb246 Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Sun, 10 May 2026 11:58:13 +0300 Subject: [PATCH] Add approval mode contract tests Cover the exact public ApprovalMode values and ensure unsupported modes fail before sync or async high-level APIs can issue client requests. Co-authored-by: Codex --- .../tests/test_public_api_runtime_behavior.py | 93 +++++++++++++++++++ .../tests/test_public_api_signatures.py | 5 +- 2 files changed, 97 insertions(+), 1 deletion(-) diff --git a/sdk/python/tests/test_public_api_runtime_behavior.py b/sdk/python/tests/test_public_api_runtime_behavior.py index e03a21d048..50e07d2331 100644 --- a/sdk/python/tests/test_public_api_runtime_behavior.py +++ b/sdk/python/tests/test_public_api_runtime_behavior.py @@ -327,6 +327,50 @@ def test_sync_api_maps_approval_modes_for_started_work() -> None: ] +def test_sync_api_rejects_unknown_approval_mode_before_rpc() -> None: + """Unknown approval modes should fail before building any client request.""" + calls: list[str] = [] + + class FailOnRpcClient: + def thread_start(self, _params: object) -> SimpleNamespace: + calls.append("thread_start") + return SimpleNamespace(thread=SimpleNamespace(id="thread-started")) + + def turn_start( + self, + _thread_id: str, + _wire_input: object, + *, + params: object | None = None, # noqa: ARG002 + ) -> SimpleNamespace: + calls.append("turn_start") + return SimpleNamespace(turn=SimpleNamespace(id="turn-1")) + + client = FailOnRpcClient() + codex = object.__new__(Codex) + codex._client = client + errors: list[str] = [] + + for call in ( + lambda: codex.thread_start(approval_mode="allow_all"), # type: ignore[arg-type] + lambda: Thread(client, "thread-1").turn( # type: ignore[arg-type] + TextInput("hello"), + approval_mode="allow_all", + ), + ): + with pytest.raises(ValueError) as exc_info: + call() + errors.append(str(exc_info.value)) + + assert (errors, calls) == ( + [ + "approval_mode must be one of: deny_all, auto_review", + "approval_mode must be one of: deny_all, auto_review", + ], + [], + ) + + def test_async_api_maps_approval_modes_for_started_work() -> None: """Async start methods should serialize only supported approval modes.""" @@ -398,6 +442,55 @@ def test_async_api_maps_approval_modes_for_started_work() -> None: asyncio.run(scenario()) +def test_async_api_rejects_unknown_approval_mode_before_rpc() -> None: + """Unknown async approval modes should fail before awaiting client calls.""" + + async def scenario() -> None: + """Exercise async validation without starting a real app-server process.""" + calls: list[str] = [] + + class FailOnAsyncRpcClient: + async def thread_start(self, _params: object) -> SimpleNamespace: + calls.append("thread_start") + return SimpleNamespace(thread=SimpleNamespace(id="thread-started")) + + async def turn_start( + self, + _thread_id: str, + _wire_input: object, + *, + params: object | None = None, # noqa: ARG002 + ) -> SimpleNamespace: + calls.append("turn_start") + return SimpleNamespace(turn=SimpleNamespace(id="turn-1")) + + codex = AsyncCodex() + codex._client = FailOnAsyncRpcClient() + codex._initialized = True + errors: list[str] = [] + + for call in ( + lambda: codex.thread_start(approval_mode="allow_all"), # type: ignore[arg-type] + lambda: AsyncThread(codex, "thread-1").turn( # type: ignore[arg-type] + TextInput("hello"), + approval_mode="allow_all", + ), + ): + with pytest.raises(ValueError) as exc_info: + await call() + errors.append(str(exc_info.value)) + + assert (errors, calls) == ( + [ + "approval_mode must be one of: deny_all, auto_review", + "approval_mode must be one of: deny_all, auto_review", + ], + [], + ) + + asyncio.run(scenario()) + + def test_turn_streams_can_consume_multiple_turns_on_one_client() -> None: """Two sync TurnHandle streams should advance independently on one client.""" client = AppServerClient() diff --git a/sdk/python/tests/test_public_api_signatures.py b/sdk/python/tests/test_public_api_signatures.py index 9b48bef99a..b8f3cd5a97 100644 --- a/sdk/python/tests/test_public_api_signatures.py +++ b/sdk/python/tests/test_public_api_signatures.py @@ -121,7 +121,10 @@ def test_root_exports_run_result() -> None: def test_root_exports_approval_mode() -> None: """The root package should expose the high-level approval mode enum.""" - assert ApprovalMode.deny_all.value == "deny_all" + assert [(mode.name, mode.value) for mode in ApprovalMode] == [ + ("deny_all", "deny_all"), + ("auto_review", "auto_review"), + ] def test_package_and_default_client_versions_follow_project_version() -> None: