test_student_release.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. """学生版的小修正回归测试:使用模拟对象,不调用真实模型、天气或数据库。"""
  2. import contextlib
  3. import importlib
  4. import io
  5. import json
  6. import os
  7. import runpy
  8. import unittest
  9. from datetime import datetime, timedelta
  10. from pathlib import Path
  11. from unittest.mock import Mock, patch
  12. ROOT = Path(__file__).resolve().parents[1]
  13. class StudentReleaseTests(unittest.TestCase):
  14. def test_course_config_uses_matching_environment_names(self):
  15. with patch.dict(os.environ, {
  16. "DEEPSEEK_API_KEY": "offline-chat-placeholder",
  17. "DASHSCOPE_API_KEY": "offline-embedding-placeholder",
  18. "QWEATHER_API_KEY": "offline-weather-placeholder",
  19. "QWEATHER_API_HOST": "weather.example.invalid",
  20. }):
  21. namespace = runpy.run_path(str(ROOT / "课件示例/项目实现/02-02.py"))
  22. config = namespace["Config"]()
  23. self.assertEqual(config.api_key, "offline-chat-placeholder")
  24. self.assertEqual(config.embedding_api_key, "offline-embedding-placeholder")
  25. self.assertEqual(config.weather_api_key, "offline-weather-placeholder")
  26. self.assertEqual(config.weather_api_host, "weather.example.invalid")
  27. def test_intent_prompt_receives_current_date(self):
  28. from main_prompts import SmartBizTripPrompts
  29. text = SmartBizTripPrompts.intent_prompt().format(
  30. query="明天天气", current_date="2026-10-09", user_profile="",
  31. task_context="{}", conversation_history="",
  32. )
  33. self.assertIn("当前日期:2026-10-09", text)
  34. def test_weather_timezone_and_update_boundary(self):
  35. from utils import spider_weather as spider
  36. now = spider.TZ.localize(datetime(2026, 10, 9, 12))
  37. with patch.object(spider, "datetime") as clock:
  38. clock.now.return_value = now
  39. self.assertFalse(spider.should_update_data((now-timedelta(hours=23, minutes=59)).replace(tzinfo=None)))
  40. self.assertTrue(spider.should_update_data((now-timedelta(hours=24)).replace(tzinfo=None)))
  41. self.assertTrue(spider.should_update_data(None))
  42. self.assertTrue(spider.should_update_data(now, force_update=True))
  43. def test_config_demo_runs_without_missing_method(self):
  44. with contextlib.redirect_stdout(io.StringIO()):
  45. runpy.run_path(str(ROOT / "config.py"), run_name="__main__")
  46. def test_ticket_task_passes_text_to_executor(self):
  47. from a2a_server import ticket_server as ticket
  48. from python_a2a import Task, Message, TextContent, MessageRole
  49. executor = Mock()
  50. executor.invoke.return_value = {"output": "未找到符合条件的车次"}
  51. message = Message(content=TextContent(text="北京到成都高铁"), role=MessageRole.USER)
  52. task = Task(id="offline-ticket-test", message=message.to_dict())
  53. with patch.object(ticket, "to_langchain_tool", return_value=[]), \
  54. patch.object(ticket, "create_tool_calling_agent"), \
  55. patch.object(ticket, "AgentExecutor", return_value=executor):
  56. result = ticket.TicketServer().handle_task(task)
  57. self.assertEqual(executor.invoke.call_args.args[0]["input"], "北京到成都高铁")
  58. self.assertEqual(result.artifacts[0]["parts"][0]["text"], "未找到符合条件的车次")
  59. def test_weather_api_start_does_not_connect_mysql(self):
  60. from mcp_server import mcp_weather_server as weather
  61. with patch.object(weather.conf, "weather_source", "api"), \
  62. patch.object(weather, "WeatherService") as db, \
  63. patch.object(weather, "create_fastapi_app", return_value="mock-app"), \
  64. patch.object(weather.uvicorn, "run") as serve, \
  65. contextlib.redirect_stdout(io.StringIO()):
  66. weather.create_weather_mcp_server()
  67. db.assert_not_called()
  68. serve.assert_called_once_with("mock-app", host="0.0.0.0", port=8002)
  69. def test_weather_http_calls_have_timeout_and_status_check(self):
  70. from mcp_server import mcp_weather_server as weather
  71. geo, forecast = Mock(), Mock()
  72. geo.json.return_value = {"location": [{"id": "101010100"}]}
  73. forecast.json.return_value = {"daily": []}
  74. with patch.object(weather.requests, "get", side_effect=[geo, forecast]) as get:
  75. result = json.loads(weather.fetch_weather_from_api("北京", "2026-10-09", "2026-10-09"))
  76. self.assertEqual(result["status"], "no_data")
  77. self.assertTrue(all(c.kwargs["timeout"] == 15 for c in get.call_args_list))
  78. geo.raise_for_status.assert_called_once()
  79. forecast.raise_for_status.assert_called_once()
  80. def test_city_filter_keeps_quotes_inside_literal(self):
  81. from mcp_server import mcp_trip_server as trip
  82. collection = Mock()
  83. collection.search.return_value = []
  84. city = '成都" or city != "北京'
  85. with patch("pymilvus.connections.connect"), \
  86. patch("pymilvus.Collection", return_value=collection), \
  87. patch.object(trip, "get_embedding", return_value=[0.0]):
  88. trip.search_meeting_packages_in_milvus("会务", city)
  89. self.assertEqual(collection.search.call_args.kwargs["expr"],
  90. "city == " + json.dumps(city, ensure_ascii=False))
  91. def test_manual_weather_script_does_not_request_on_import(self):
  92. with patch("requests.get") as get:
  93. runpy.run_path(str(ROOT / "test.py"), run_name="manual_weather_import_test")
  94. get.assert_not_called()
  95. def test_imports_stay_in_this_copy(self):
  96. for name in ("config", "chat_service", "mcp_server.mcp_weather_server",
  97. "mcp_server.mcp_ticket_server", "mcp_server.mcp_trip_server"):
  98. module = importlib.import_module(name)
  99. self.assertTrue(Path(module.__file__).resolve().is_relative_to(ROOT))