test_generator_agent.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. """GeneratorAgent 纯逻辑部分(prompt 构造 / 响应解析 / 用例规范化)的单元测试
  2. 注意:这里只测不依赖 LLM 的方法,用 object.__new__ 跳过 __init__,
  3. 避免真正创建 HelloAgentsLLM 实例。
  4. """
  5. from src.agents.generator_agent import GeneratorAgent
  6. from src.agents.parser_agent import ParserAgent
  7. def _gen():
  8. # 不调用 __init__(__init__ 会创建 LLM 实例并读 .env),只测纯函数
  9. g = object.__new__(GeneratorAgent)
  10. # _normalize_case/_decide_case_types 依赖 parser,手动补上
  11. g.parser = ParserAgent()
  12. return g
  13. # --- _parse_response ---
  14. def test_parse_response_clean_json():
  15. cases = _gen()._parse_response('[{"name": "a", "case_type": "normal"}]')
  16. assert isinstance(cases, list)
  17. assert len(cases) == 1
  18. assert cases[0]["name"] == "a"
  19. def test_parse_response_with_markdown_fence():
  20. resp = '```json\n[{"name": "a", "case_type": "normal"}]\n```'
  21. cases = _gen()._parse_response(resp)
  22. assert len(cases) == 1
  23. assert cases[0]["name"] == "a"
  24. def test_parse_response_with_extra_text():
  25. resp = '好的,以下是测试用例:\n[{"name": "a"}]\n希望有帮助'
  26. cases = _gen()._parse_response(resp)
  27. assert len(cases) == 1
  28. def test_parse_response_invalid_json():
  29. assert _gen()._parse_response("没有 JSON 数组") == []
  30. # --- _build_prompt ---
  31. def test_build_prompt_contains_endpoint_fields():
  32. ep = {
  33. "path": "/users",
  34. "method": "GET",
  35. "parameters": [],
  36. "request_body": None,
  37. "responses": {"200": {}},
  38. }
  39. prompt = _gen()._build_prompt(ep, ["normal"])
  40. assert "/users" in prompt
  41. assert "GET" in prompt
  42. # --- _decide_case_types ---
  43. def test_decide_case_types_no_input_only_normal():
  44. ep = {"parameters": [], "request_body": None, "responses": {"200": {}}}
  45. assert _gen()._decide_case_types(ep) == ["normal"]
  46. def test_decide_case_types_with_body():
  47. ep = {
  48. "parameters": [],
  49. "request_body": {"required": ["name"], "properties": {"name": {"type": "string"}}},
  50. "responses": {"201": {}, "422": {}},
  51. }
  52. # 有请求体 → 有 boundary(可测输入)和 error(必填字段)
  53. assert _gen()._decide_case_types(ep) == ["normal", "boundary", "error"]
  54. def test_decide_case_types_path_param_only():
  55. ep = {
  56. "parameters": [{"name": "id", "in": "path", "required": True, "schema": {"type": "string"}}],
  57. "request_body": None,
  58. "responses": {"200": {}, "422": {}},
  59. }
  60. # 只有路径参数 → 有 error(缺路径参数→404),但没有 boundary(路径参数不可取边界值)
  61. assert _gen()._decide_case_types(ep) == ["normal", "error"]
  62. # --- _normalize_case ---
  63. def test_normalize_case_overrides_llm_status_and_injects_schema():
  64. ep = {
  65. "path": "/register",
  66. "method": "POST",
  67. "parameters": [],
  68. "request_body": {"required": ["name"]},
  69. "request_content_type": "application/json",
  70. "responses": {
  71. "201": {
  72. "content": {
  73. "application/json": {
  74. "schema": {"type": "object", "properties": {"id": {"type": "integer"}}}
  75. }
  76. }
  77. },
  78. "422": {},
  79. },
  80. }
  81. # LLM 把 expected_status 写错成 400,规范化后应被覆盖成文档的 201
  82. case = {"name": "注册", "case_type": "normal", "body": {"name": "x"}, "expected_status": 400}
  83. out = _gen()._normalize_case(case, ep)
  84. assert out["path"] == "/register"
  85. assert out["method"] == "POST"
  86. assert out["expected_status"] == 201
  87. # 期望响应 schema 被注入
  88. assert out["expected_schema"]["properties"]["id"]["type"] == "integer"
  89. assert out["content_type"] == "application/json"
  90. def test_normalize_case_error_status():
  91. ep = {
  92. "path": "/x",
  93. "method": "POST",
  94. "parameters": [],
  95. "request_body": {"required": ["name"]},
  96. "request_content_type": "application/json",
  97. "responses": {"201": {}, "422": {}},
  98. }
  99. case = {"name": "缺字段", "case_type": "error", "body": {}, "expected_status": 500}
  100. out = _gen()._normalize_case(case, ep)
  101. assert out["expected_status"] == 422
  102. def test_normalize_case_invalid_case_type_falls_back():
  103. ep = {
  104. "path": "/x", "method": "GET", "parameters": [], "request_body": None,
  105. "responses": {"200": {}},
  106. }
  107. case = {"name": "x", "case_type": "whatever", "body": "not dict"}
  108. out = _gen()._normalize_case(case, ep)
  109. assert out["case_type"] == "normal"
  110. # body 非 dict 被兜底成空 dict,避免后续 multipart 拆分报错
  111. assert out["body"] == {}
  112. # --- _build_files ---
  113. def test_build_files_extracts_file_field():
  114. ep = {
  115. "request_body": {
  116. "type": "object",
  117. "required": ["file"],
  118. "properties": {"file": {"type": "string", "contentMediaType": "application/octet-stream"}},
  119. }
  120. }
  121. body = {"file": "test_image.jpg"} # LLM 给的任意文件名
  122. files = _gen()._build_files(ep, body)
  123. assert files is not None
  124. assert "file" in files
  125. filename, content, mime = files["file"]
  126. # 占位内容固定是 PNG,文件名强制 .png,避免扩展名和 MIME 不一致导致 415
  127. assert filename == "file.png"
  128. assert isinstance(content, bytes) and content # 非空字节
  129. assert mime == "image/png"
  130. # 文件字段从 body 里弹出,body 不再含它
  131. assert "file" not in body
  132. def test_build_files_no_file_field_returns_none():
  133. ep = {
  134. "request_body": {
  135. "type": "object",
  136. "properties": {"name": {"type": "string"}},
  137. }
  138. }
  139. body = {"name": "x"}
  140. assert _gen()._build_files(ep, body) is None
  141. assert body == {"name": "x"}