|
27 | 27 | # 显式在模块加载时触发补丁 (幂等, 与 SuperAgentClient.__init__ 内触发点一致)。 |
28 | 28 | ensure_super_agent_patches_applied() |
29 | 29 |
|
| 30 | +# ─── _prune_forwarded_props ─────────────────────────────────── |
| 31 | + |
| 32 | + |
| 33 | +def test_prune_forwarded_props_removes_none_scalars(): |
| 34 | + from agentrun.super_agent.api.control import _prune_forwarded_props |
| 35 | + |
| 36 | + out = _prune_forwarded_props({"a": None, "b": "x"}) |
| 37 | + assert out == {"b": "x"} |
| 38 | + |
| 39 | + |
| 40 | +def test_prune_forwarded_props_removes_empty_lists(): |
| 41 | + from agentrun.super_agent.api.control import _prune_forwarded_props |
| 42 | + |
| 43 | + out = _prune_forwarded_props({"a": [], "b": ["x"]}) |
| 44 | + assert out == {"b": ["x"]} |
| 45 | + |
| 46 | + |
| 47 | +def test_prune_forwarded_props_keeps_keep_keys_even_when_none(): |
| 48 | + from agentrun.super_agent.api.control import _prune_forwarded_props |
| 49 | + |
| 50 | + out = _prune_forwarded_props( |
| 51 | + {"metadata": None, "other": None}, keep_keys=("metadata",) |
| 52 | + ) |
| 53 | + assert out == {"metadata": None} |
| 54 | + |
| 55 | + |
| 56 | +def test_prune_forwarded_props_keeps_keep_keys_even_when_empty_list(): |
| 57 | + from agentrun.super_agent.api.control import _prune_forwarded_props |
| 58 | + |
| 59 | + out = _prune_forwarded_props( |
| 60 | + {"metadata": [], "other": []}, keep_keys=("metadata",) |
| 61 | + ) |
| 62 | + assert out == {"metadata": []} |
| 63 | + |
| 64 | + |
| 65 | +def test_prune_forwarded_props_preserves_falsy_non_none_scalars(): |
| 66 | + """0, False, "" 不应被剔除 (只有 None 或空 list).""" |
| 67 | + from agentrun.super_agent.api.control import _prune_forwarded_props |
| 68 | + |
| 69 | + out = _prune_forwarded_props({"n": 0, "b": False, "s": ""}) |
| 70 | + assert out == {"n": 0, "b": False, "s": ""} |
| 71 | + |
| 72 | + |
| 73 | +def test_prune_forwarded_props_preserves_non_empty_lists_and_dicts(): |
| 74 | + from agentrun.super_agent.api.control import _prune_forwarded_props |
| 75 | + |
| 76 | + out = _prune_forwarded_props({ |
| 77 | + "list": ["x"], |
| 78 | + "dict_empty": {}, # dict 不算 list, 保留 |
| 79 | + "dict_full": {"k": "v"}, |
| 80 | + }) |
| 81 | + assert out == {"list": ["x"], "dict_empty": {}, "dict_full": {"k": "v"}} |
| 82 | + |
| 83 | + |
30 | 84 | # ─── build_super_agent_endpoint ──────────────────────────────── |
31 | 85 |
|
32 | 86 |
|
@@ -116,11 +170,50 @@ def test_to_create_input_minimal(): |
116 | 170 | cfg_dict = json.loads(settings[0]["config"]) |
117 | 171 | assert cfg_dict["path"] == "/invoke" |
118 | 172 | assert cfg_dict["headers"] == {} |
| 173 | + # 具体字段缺席断言放到 test_to_create_input_minimal_omits_unset_scalar_and_empty_list_fields |
119 | 174 | forwarded = cfg_dict["body"]["forwardedProps"] |
120 | | - assert forwarded["agents"] == [] |
121 | 175 | assert forwarded["metadata"] == {"agentRuntimeName": "alpha"} |
122 | 176 |
|
123 | 177 |
|
| 178 | +def test_to_create_input_minimal_omits_unset_scalar_and_empty_list_fields(): |
| 179 | + """create 时, 未设置的 scalar 字段和空 list 字段 MUST NOT 出现在 forwardedProps 里.""" |
| 180 | + cfg = Config(account_id="123", region_id="cn-hangzhou") |
| 181 | + inp = to_create_input("alpha", cfg=cfg) |
| 182 | + cfg_dict = json.loads( |
| 183 | + inp.protocol_configuration.protocol_settings[0]["config"] |
| 184 | + ) |
| 185 | + forwarded = cfg_dict["body"]["forwardedProps"] |
| 186 | + # metadata 永远保留 |
| 187 | + assert forwarded["metadata"] == {"agentRuntimeName": "alpha"} |
| 188 | + # 未设置的 scalar 字段缺席 |
| 189 | + assert "prompt" not in forwarded |
| 190 | + assert "modelServiceName" not in forwarded |
| 191 | + assert "modelName" not in forwarded |
| 192 | + # 空 list 字段缺席 |
| 193 | + assert "agents" not in forwarded |
| 194 | + assert "tools" not in forwarded |
| 195 | + assert "skills" not in forwarded |
| 196 | + assert "sandboxes" not in forwarded |
| 197 | + assert "workspaces" not in forwarded |
| 198 | + |
| 199 | + |
| 200 | +def test_to_create_input_partial_only_keeps_set_fields(): |
| 201 | + """仅设置部分字段时, 未设置的字段不出现, 已设置的字段按原值出现.""" |
| 202 | + cfg = Config(account_id="123", region_id="cn-hangzhou") |
| 203 | + inp = to_create_input( |
| 204 | + "bravo", prompt="hello", model_service_name="svc", cfg=cfg |
| 205 | + ) |
| 206 | + cfg_dict = json.loads( |
| 207 | + inp.protocol_configuration.protocol_settings[0]["config"] |
| 208 | + ) |
| 209 | + forwarded = cfg_dict["body"]["forwardedProps"] |
| 210 | + assert forwarded["prompt"] == "hello" |
| 211 | + assert forwarded["modelServiceName"] == "svc" |
| 212 | + assert "modelName" not in forwarded |
| 213 | + assert "agents" not in forwarded |
| 214 | + assert forwarded["metadata"] == {"agentRuntimeName": "bravo"} |
| 215 | + |
| 216 | + |
124 | 217 | def test_to_create_input_full(): |
125 | 218 | cfg = Config(account_id="123", region_id="cn-hangzhou") |
126 | 219 | inp = to_create_input( |
@@ -382,6 +475,62 @@ def test_to_update_input_full_protocol_replace(): |
382 | 475 | assert forwarded["tools"] == ["t"] |
383 | 476 |
|
384 | 477 |
|
| 478 | +def test_to_update_input_keeps_null_for_none_scalars(): |
| 479 | + """update 路径: 合并后为 None 的 scalar 字段 MUST 仍写 null (不剪除). |
| 480 | +
|
| 481 | + 保证 SDK 的 'update(model_name=None) 表示清空' 语义不被本次 PR 破坏。 |
| 482 | + """ |
| 483 | + cfg = Config(account_id="123", region_id="cn-hangzhou") |
| 484 | + merged = { |
| 485 | + "prompt": None, |
| 486 | + "agents": [], |
| 487 | + "tools": [], |
| 488 | + "skills": [], |
| 489 | + "sandboxes": [], |
| 490 | + "workspaces": [], |
| 491 | + "model_service_name": None, |
| 492 | + "model_name": None, |
| 493 | + } |
| 494 | + inp = to_update_input("u1", merged, cfg=cfg) |
| 495 | + cfg_dict = json.loads( |
| 496 | + inp.protocol_configuration.protocol_settings[0]["config"] |
| 497 | + ) |
| 498 | + forwarded = cfg_dict["body"]["forwardedProps"] |
| 499 | + # 明确包含 null (未被剪除) |
| 500 | + assert "prompt" in forwarded and forwarded["prompt"] is None |
| 501 | + assert ( |
| 502 | + "modelServiceName" in forwarded |
| 503 | + and forwarded["modelServiceName"] is None |
| 504 | + ) |
| 505 | + assert "modelName" in forwarded and forwarded["modelName"] is None |
| 506 | + # 空 list 也保留 (update 语义下 [] 代表 "清空列表", 不能剪除) |
| 507 | + assert forwarded["agents"] == [] |
| 508 | + assert forwarded["tools"] == [] |
| 509 | + |
| 510 | + |
| 511 | +def test_to_update_input_keeps_values_and_nulls_mixed(): |
| 512 | + """update: 有些字段有值, 有些是 None, 都应完整出现在 payload.""" |
| 513 | + cfg = Config(account_id="123", region_id="cn-hangzhou") |
| 514 | + merged = { |
| 515 | + "prompt": "new", |
| 516 | + "agents": ["a"], |
| 517 | + "model_service_name": None, |
| 518 | + "model_name": "m", |
| 519 | + } |
| 520 | + inp = to_update_input("u2", merged, cfg=cfg) |
| 521 | + cfg_dict = json.loads( |
| 522 | + inp.protocol_configuration.protocol_settings[0]["config"] |
| 523 | + ) |
| 524 | + forwarded = cfg_dict["body"]["forwardedProps"] |
| 525 | + assert forwarded["prompt"] == "new" |
| 526 | + assert forwarded["agents"] == ["a"] |
| 527 | + assert ( |
| 528 | + "modelServiceName" in forwarded |
| 529 | + and forwarded["modelServiceName"] is None |
| 530 | + ) |
| 531 | + assert forwarded["modelName"] == "m" |
| 532 | + |
| 533 | + |
385 | 534 | # ─── Dara ListAgentRuntimesRequest systemTags 原生字段 ────────────── |
386 | 535 | # ``systemTags`` 已由 Dara SDK 原生支持, 无需补丁。以下测试只校验 pydantic → |
387 | 536 | # Dara roundtrip 能把 ``system_tags`` 保留到请求 query。 |
|
0 commit comments