test_main_fan_targets.py 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. """简短业务入口:身份解析、环境隔离、网关默认策略与未决身份阻断。"""
  2. import json
  3. from pathlib import Path
  4. from types import SimpleNamespace
  5. import pytest
  6. from extensions.main_fan.schemas import MainFanInput, MainFanResult
  7. from extensions.main_fan.targets import load_catalog, TargetResolutionError
  8. from extensions.main_fan.tools import analyze_main_fan
  9. @pytest.fixture
  10. def target_catalog(monkeypatch):
  11. root=Path(__file__).resolve().parents[1]
  12. monkeypatch.setenv('MAIN_FAN_TARGETS_FILE',str(root/'examples/main_fan/mock_targets.json'))
  13. monkeypatch.setenv('SHANXI_GATEWAY_URL','http://127.0.0.1:8766')
  14. monkeypatch.setenv('SHANXI_GATEWAY_TOKEN','synthetic-test-token')
  15. monkeypatch.setenv('SHANXI_GATEWAY_ENABLED','1')
  16. monkeypatch.setenv('MAIN_FAN_DEFAULT_DOCUMENT_CACHE_POLICY','refresh')
  17. @pytest.mark.parametrize('value',[
  18. {'target':'众源煤业1号主通风机'},
  19. {'target':'mock-report-fan-1'},
  20. '{"target":"众源煤业1号主通风机"}',
  21. '审核众源煤业1号主通风机',
  22. '请对陕西众源煤业集团有限公司的一号主通风机进行研判',
  23. {'mine_name':'众源煤业','fan_name':'1#通风机'},
  24. {'fan_id':'mock-report-fan-1'},
  25. {'fanId':'mock-report-fan-1'},
  26. ])
  27. def test_single_target_resolves_configured_identity_and_internal_defaults(target_catalog,value):
  28. result=MainFanInput.model_validate(value)
  29. assert result.mine_code=='mock-zy'
  30. assert result.mine_name=='陕西众源煤业集团有限公司'
  31. assert result.fan_id=='mock-report-fan-1'
  32. assert result.fan_name=='1号通风机'
  33. assert result.use_gateway is True and result.document_cache_policy=='refresh'
  34. assert result.reports==[] and result.points==[] and result.confirmed_bindings==[]
  35. def test_second_fan_is_not_replaced_with_first_fan(target_catalog):
  36. result=MainFanInput.model_validate({'target':'审核众源煤业二号主通风机'})
  37. assert result.fan_id=='mock-report-fan-2' and result.fan_name=='2号通风机'
  38. @pytest.mark.parametrize('value',[
  39. {'target':'众源煤业3号主通风机'},
  40. {'target':'其他煤矿1号主通风机'},
  41. {'target':'众源煤业1号主通风机','mine_code':'other-mine'},
  42. {'target':'众源煤业1号主通风机','mine_name':'其他矿井'},
  43. {'target':'众源煤业1号主通风机','fan_id':'mock-report-fan-2'},
  44. {'target':'众源煤业1号主通风机','fan_name':'2号通风机'},
  45. {'target':'众源煤业1号主通风机','mine_code':123},
  46. {'target':'众源煤业1号主通风机','mine_code':'mock-zy','mineCode':'other'},
  47. '{"target":',
  48. ])
  49. def test_ambiguous_unknown_or_conflicting_identity_is_rejected(target_catalog,value):
  50. with pytest.raises(ValueError):MainFanInput.model_validate(value)
  51. def test_mock_identity_catalog_cannot_be_used_with_real_gateway(target_catalog,monkeypatch):
  52. monkeypatch.setenv('SHANXI_GATEWAY_URL','https://real-gateway.invalid')
  53. with pytest.raises(ValueError,match='绑定的网关'):load_catalog()
  54. with pytest.raises(ValueError,match='绑定的网关'):
  55. MainFanInput.model_validate({'target':'众源煤业1号主通风机'})
  56. def test_target_resolution_without_catalog_never_fabricates_codes(monkeypatch):
  57. monkeypatch.delenv('MAIN_FAN_TARGETS_FILE',raising=False)
  58. with pytest.raises(ValueError):MainFanInput.model_validate({'target':'众源煤业1号主通风机'})
  59. def test_explicit_local_material_and_cache_choices_are_preserved(target_catalog):
  60. result=MainFanInput.model_validate({'target':'众源煤业1号主通风机',
  61. 'materials':[{'path':'/workspace/uploads/raw.pdf'}],'document_cache_policy':'off'})
  62. assert result.use_gateway is False and result.document_cache_policy=='off'
  63. explicit=MainFanInput.model_validate({'target':'众源煤业1号主通风机',
  64. 'use_gateway':False,'document_cache_policy':'auto'})
  65. assert explicit.use_gateway is False and explicit.document_cache_policy=='auto'
  66. def test_disabling_gateway_does_not_activate_mock_implicitly(target_catalog,monkeypatch):
  67. monkeypatch.setenv('SHANXI_GATEWAY_ENABLED','0')
  68. assert MainFanInput.model_validate({'target':'众源煤业1号主通风机'}).use_gateway is False
  69. def test_complete_input_and_camel_case_remain_compatible(monkeypatch):
  70. monkeypatch.delenv('MAIN_FAN_TARGETS_FILE',raising=False)
  71. value=MainFanInput.model_validate({'mineCode':'formal-code','mineName':'正式矿井',
  72. 'fanId':'formal-fan','fan_name':'1号通风机','use_gateway':False})
  73. assert value.mine_code=='formal-code' and value.fan_id=='formal-fan'
  74. assert value.document_cache_policy=='auto'
  75. @pytest.mark.asyncio
  76. async def test_short_tool_call_passes_server_resolved_parameters(target_catalog,monkeypatch):
  77. seen=[]
  78. async def fake_analyze(data,user,session,**kwargs):
  79. seen.append((data,user,session))
  80. return MainFanResult(status='needs_materials',mine_code=data['mine_code'],mine_name=data['mine_name'],
  81. check_object=data['fan_name'],result_title='测试',result='仅验证输入路由').model_dump(mode='json')
  82. monkeypatch.setattr('extensions.main_fan.service.analyze',fake_analyze)
  83. runtime=SimpleNamespace(config={'configurable':{'workspace_user':'test','workspace_session':'test-session'}})
  84. await analyze_main_fan('审核众源煤业1号主通风机',runtime)
  85. data,user,session=seen[0]
  86. assert data['fan_id']=='mock-report-fan-1' and data['use_gateway'] is True
  87. assert data['document_cache_policy']=='refresh' and not data.get('reports')
  88. assert (user,session)==('test','test-session')
  89. @pytest.mark.asyncio
  90. async def test_ambiguous_tool_target_never_calls_gateway_or_analyzer(target_catalog,monkeypatch):
  91. from extensions.main_fan.targets import TargetCatalog
  92. content=load_catalog().model_dump()
  93. for record in content['targets']:record.pop('fan_pair')
  94. monkeypatch.setattr('extensions.main_fan.targets.load_catalog',lambda:TargetCatalog.model_validate(content))
  95. async def forbidden(*args,**kwargs):raise AssertionError('身份未定不得分析')
  96. monkeypatch.setattr('extensions.main_fan.service.analyze',forbidden)
  97. runtime=SimpleNamespace(config={'configurable':{'workspace_user':'test','workspace_session':'test-session'}})
  98. result=await analyze_main_fan('审核众源煤业主通风机',runtime)
  99. assert result['status']=='needs_materials' and not result['analysis_id']
  100. assert '1号通风机' in result['result'] and '2号通风机' in result['result']
  101. @pytest.mark.parametrize('value',[{'target':'众源煤业'},{'mine_code':'mock-zy'},'审核众源煤业主通风机','审核众源煤业主通风机及备用是否一致'])
  102. def test_mine_request_resolves_one_configured_fan_pair(target_catalog,value):
  103. result=MainFanInput.model_validate(value)
  104. assert result.fan_pair==['mock-report-fan-1','mock-report-fan-2']
  105. assert result.paired_assessment is True
  106. def test_job_api_accepts_one_target_parameter(target_catalog,monkeypatch):
  107. from runtime.business_tasks import submit
  108. from db import business_store
  109. monkeypatch.setenv('SHANXI_GATEWAY_ENABLED','0')
  110. job=submit('main_fan',{'target':'众源煤业1号主通风机'},user_name='one-parameter',publish=False)
  111. stored=json.loads(business_store.get(job['id'])['payload'])['input']
  112. assert stored['mine_code']=='mock-zy' and stored['fan_id']=='mock-report-fan-1'
  113. assert stored['document_cache_policy']=='refresh'