"""Round 11 — cheap_codex_smoke 加 model 参数 + 读完整 SSE 拿真实对话内容。 覆盖: - model 默认值 gpt-5.5 (Round 11 v2 升级 — team-only 主路径) - model 入参可定制(gpt-5.5 团队号 / gpt-5.4 通用号) - SSE 完整读出 response.completed,detail 升级为 dict 含 response_text - 8 行内未见 response.created → uncertain - 30 帧仍无 completed → alive 兜底(标 raw_event=no_completed_within_30_frames) Round 11 v2 schema 升级注:max_output_tokens 不再写入 payload(后端拒收), 完整 v2 schema 测试见 tests/unit/test_round11_cheap_codex_smoke_v2.py。 """ from __future__ import annotations from unittest.mock import MagicMock, patch def _make_sse_resp(status_code: int, lines): fake_resp = MagicMock() fake_resp.status_code = status_code fake_resp.iter_lines.return_value = iter(lines) return fake_resp def test_smoke_with_custom_model_param_passes_through(): """传 model='gpt-5.5' → payload['model'] == 'gpt-5.5'。""" from autoteam import codex_auth captured = {} def fake_post(*args, **kwargs): captured["payload"] = kwargs.get("json") return _make_sse_resp(200, [ "", 'event: response.created', 'data: {"type": "response.created"}', 'data: {"type": "response.completed", "response": {"usage": {"output_tokens": 5}}}', ]) with patch("requests.post", side_effect=fake_post): result, detail = codex_auth.cheap_codex_smoke( "test-token", account_id="acc-1", model="gpt-5.5", force=True, ) assert result == "alive" assert captured["payload"]["model"] == "gpt-5.5" assert isinstance(detail, dict) assert detail.get("model") == "gpt-5.5" def test_smoke_default_model_gpt55(): """Round 11 v2:不传 model → 默认 gpt-5.5(team-only 主路径,见 PRD Q4)。""" from autoteam import codex_auth captured = {} def fake_post(*args, **kwargs): captured["payload"] = kwargs.get("json") return _make_sse_resp(200, [ 'data: {"type": "response.created"}', 'data: {"type": "response.completed"}', ]) with patch("requests.post", side_effect=fake_post): result, _detail = codex_auth.cheap_codex_smoke("tok", account_id="acc-1", force=True) assert result == "alive" assert captured["payload"]["model"] == "gpt-5.5" def test_smoke_returns_response_text_in_dict(): """SSE 多帧 delta + completed → detail 是 dict,response_text 是拼接后的真实文本。""" from autoteam import codex_auth sse_lines = [ "", 'event: response.created', 'data: {"type": "response.created"}', 'data: {"type": "response.output_text.delta", "delta": "Hello"}', 'data: {"type": "response.output_text.delta", "delta": ", "}', 'data: {"type": "response.output_text.delta", "delta": "world!"}', 'data: {"type": "response.completed", "response": {"usage": {"output_tokens": 3}}}', ] with patch("requests.post", return_value=_make_sse_resp(200, sse_lines)): result, detail = codex_auth.cheap_codex_smoke( "tok", account_id="acc-1", model="gpt-5.4", force=True, ) assert result == "alive" assert isinstance(detail, dict) assert detail["response_text"] == "Hello, world!" assert detail["raw_event"] == "response.completed" assert detail["tokens"] == 3 assert detail["model"] == "gpt-5.4" def test_smoke_no_response_created_returns_uncertain(): """8 行内仍无 response.created → uncertain 兜底。""" from autoteam import codex_auth sse_lines = ["", "noise", "more noise", ""] with patch("requests.post", return_value=_make_sse_resp(200, sse_lines)): result, detail = codex_auth.cheap_codex_smoke("tok", account_id="acc-1", force=True) assert result == "uncertain" assert "no_response_created_frame" in (detail or "") def test_smoke_max_output_tokens_no_longer_in_payload(): """Round 11 v2:max_output_tokens 不再写入 payload(后端拒收),但函数签名保留。""" from autoteam import codex_auth captured = {} def fake_post(*args, **kwargs): captured["payload"] = kwargs.get("json") return _make_sse_resp(200, [ 'data: {"type": "response.created"}', 'data: {"type": "response.completed"}', ]) with patch("requests.post", side_effect=fake_post): # 传 max_output_tokens 不抛(签名仍接受),但 payload 不会含此 key codex_auth.cheap_codex_smoke( "tok", account_id="acc-1", model="gpt-5.5", max_output_tokens=128, force=True, ) assert "max_output_tokens" not in captured["payload"] def test_smoke_auth_invalid_path_unchanged(): """401/403/429 → auth_invalid 路径不动(向后兼容)。""" from autoteam import codex_auth fake_resp = MagicMock() fake_resp.status_code = 401 with patch("requests.post", return_value=fake_resp): result, detail = codex_auth.cheap_codex_smoke("tok", account_id="acc-1", force=True) assert result == "auth_invalid" assert detail == "http_401"