File size: 5,267 Bytes
090976a
 
 
cf83da4
090976a
 
 
 
cf83da4
 
 
090976a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
cf83da4
 
090976a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
cf83da4
090976a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
cf83da4
 
090976a
 
 
 
 
 
 
 
 
 
 
 
cf83da4
090976a
cf83da4
090976a
 
cf83da4
090976a
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
"""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"