mirror of
https://github.com/openai/codex.git
synced 2026-05-23 20:44:50 +00:00
Port SDK behavior tests to app-server harness
Move result extraction, stream_text, approval inheritance, model list, and compact coverage onto the pinned app-server integration harness so the remaining unit tests stay focused on generated models and transport internals. Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
@@ -2,11 +2,9 @@ from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import time
|
||||
from types import SimpleNamespace
|
||||
|
||||
from openai_codex.async_client import AsyncAppServerClient
|
||||
from openai_codex.generated.v2_all import (
|
||||
AgentMessageDeltaNotification,
|
||||
TurnCompletedNotification,
|
||||
)
|
||||
from openai_codex.models import Notification, UnknownNotification
|
||||
@@ -36,46 +34,6 @@ def test_async_client_allows_concurrent_transport_calls() -> None:
|
||||
assert asyncio.run(scenario()) == 2
|
||||
|
||||
|
||||
def test_async_stream_text_is_incremental_without_blocking_parallel_calls() -> None:
|
||||
"""Async text streaming should yield incrementally without blocking other calls."""
|
||||
async def scenario() -> tuple[str, list[str], bool]:
|
||||
"""Start a stream, then prove another async client call can finish."""
|
||||
client = AsyncAppServerClient()
|
||||
|
||||
def fake_stream_text(thread_id: str, text: str, params=None): # type: ignore[no-untyped-def]
|
||||
"""Yield one item before sleeping so the async wrapper can interleave."""
|
||||
yield "first"
|
||||
time.sleep(0.03)
|
||||
yield "second"
|
||||
yield "third"
|
||||
|
||||
def fake_model_list(include_hidden: bool = False) -> str:
|
||||
"""Return immediately to prove the event loop was not monopolized."""
|
||||
return "done"
|
||||
|
||||
client._sync.stream_text = fake_stream_text # type: ignore[method-assign]
|
||||
client._sync.model_list = fake_model_list # type: ignore[method-assign]
|
||||
|
||||
stream = client.stream_text("thread-1", "hello")
|
||||
first = await anext(stream)
|
||||
|
||||
competing_call = asyncio.create_task(client.model_list())
|
||||
await asyncio.sleep(0.01)
|
||||
competing_call_done_before_stream_done = competing_call.done()
|
||||
|
||||
remaining: list[str] = []
|
||||
async for item in stream:
|
||||
remaining.append(item)
|
||||
|
||||
await competing_call
|
||||
return first, remaining, competing_call_done_before_stream_done
|
||||
|
||||
first, remaining, was_unblocked = asyncio.run(scenario())
|
||||
assert first == "first"
|
||||
assert remaining == ["second", "third"]
|
||||
assert was_unblocked
|
||||
|
||||
|
||||
def test_async_client_turn_notification_methods_delegate_to_sync_client() -> None:
|
||||
"""Async turn routing methods should preserve sync-client registration semantics."""
|
||||
async def scenario() -> tuple[list[tuple[str, str]], Notification, str]:
|
||||
@@ -142,84 +100,3 @@ def test_async_client_turn_notification_methods_delegate_to_sync_client() -> Non
|
||||
),
|
||||
"turn-1",
|
||||
)
|
||||
|
||||
|
||||
def test_async_stream_text_uses_sync_turn_routing() -> None:
|
||||
"""Async text streaming should consume the same per-turn routing path as sync."""
|
||||
async def scenario() -> tuple[list[tuple[str, str]], list[str]]:
|
||||
"""Record routing calls while streaming two deltas and one completion."""
|
||||
client = AsyncAppServerClient()
|
||||
notifications = [
|
||||
Notification(
|
||||
method="item/agentMessage/delta",
|
||||
payload=AgentMessageDeltaNotification.model_validate(
|
||||
{
|
||||
"delta": "first",
|
||||
"itemId": "item-1",
|
||||
"threadId": "thread-1",
|
||||
"turnId": "turn-1",
|
||||
}
|
||||
),
|
||||
),
|
||||
Notification(
|
||||
method="item/agentMessage/delta",
|
||||
payload=AgentMessageDeltaNotification.model_validate(
|
||||
{
|
||||
"delta": "second",
|
||||
"itemId": "item-2",
|
||||
"threadId": "thread-1",
|
||||
"turnId": "turn-1",
|
||||
}
|
||||
),
|
||||
),
|
||||
Notification(
|
||||
method="turn/completed",
|
||||
payload=TurnCompletedNotification.model_validate(
|
||||
{
|
||||
"threadId": "thread-1",
|
||||
"turn": {"id": "turn-1", "items": [], "status": "completed"},
|
||||
}
|
||||
),
|
||||
),
|
||||
]
|
||||
calls: list[tuple[str, str]] = []
|
||||
|
||||
def fake_turn_start(thread_id: str, text: str, *, params=None): # type: ignore[no-untyped-def]
|
||||
"""Return a started turn id while recording the request thread."""
|
||||
calls.append(("turn_start", thread_id))
|
||||
return SimpleNamespace(turn=SimpleNamespace(id="turn-1"))
|
||||
|
||||
def fake_register(turn_id: str) -> None:
|
||||
"""Record stream registration for the started turn."""
|
||||
calls.append(("register", turn_id))
|
||||
|
||||
def fake_next(turn_id: str) -> Notification:
|
||||
"""Return the next queued turn notification."""
|
||||
calls.append(("next", turn_id))
|
||||
return notifications.pop(0)
|
||||
|
||||
def fake_unregister(turn_id: str) -> None:
|
||||
"""Record stream cleanup for the started turn."""
|
||||
calls.append(("unregister", turn_id))
|
||||
|
||||
client._sync.turn_start = fake_turn_start # type: ignore[method-assign]
|
||||
client._sync.register_turn_notifications = fake_register # type: ignore[method-assign]
|
||||
client._sync.next_turn_notification = fake_next # type: ignore[method-assign]
|
||||
client._sync.unregister_turn_notifications = fake_unregister # type: ignore[method-assign]
|
||||
|
||||
chunks = [chunk async for chunk in client.stream_text("thread-1", "hello")]
|
||||
return calls, [chunk.delta for chunk in chunks]
|
||||
|
||||
calls, deltas = asyncio.run(scenario())
|
||||
|
||||
assert (calls, deltas) == (
|
||||
[
|
||||
("turn_start", "thread-1"),
|
||||
("register", "turn-1"),
|
||||
("next", "turn-1"),
|
||||
("next", "turn-1"),
|
||||
("next", "turn-1"),
|
||||
("unregister", "turn-1"),
|
||||
],
|
||||
["first", "second"],
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user