from __future__ import annotations import json from pathlib import Path import tempfile import unittest from unittest.mock import patch from src.poc4.scenarios import ScenarioConfigError, load_demo_scenarios from src.agent_console.profile import load_app_profile from src.mcp_tool_router import McpTool, build_mcp_tool_arguments from src.mcp_result import ( has_actionable_text_result, status_result_evidence, status_result_summary, ) def _load_poc4_query_helpers(): """Load the Streamlit entrypoint only when its optional runtime is installed.""" try: from apps.poc4.mcp_discovery_ui import _prepare_hmm_hr_tool_query except ModuleNotFoundError: return None return _prepare_hmm_hr_tool_query class DemoScenarioConfigTest(unittest.TestCase): def test_profile_environment_overrides_json_defaults(self) -> None: path = Path(__file__).parents[1] / "config" / "app_profile.json" with patch.dict( "os.environ", { "AGENT_CONSOLE_SHORT_NAME": "HMM", "AGENT_CONSOLE_PAGE_TITLE": "HMM AI 업무 에이전트", "AGENT_CONSOLE_PRIMARY_COLOR": "#003b70", }, clear=False, ): profile = load_app_profile(path) self.assertEqual(profile.short_name, "HMM") self.assertEqual(profile.page_title, "HMM AI 업무 에이전트") self.assertEqual(profile.primary_color, "#003b70") def test_profile_reads_dotenv_values(self) -> None: path = Path(__file__).parents[1] / "config" / "app_profile.json" with tempfile.TemporaryDirectory() as temp_dir: env_file = Path(temp_dir) / ".env" env_file.write_text("AGENT_CONSOLE_SHORT_NAME=HMM\n", encoding="utf-8") profile = load_app_profile(path, env_file) self.assertEqual(profile.short_name, "HMM") def test_common_theme_covers_lists_expanders_and_secondary_buttons(self) -> None: path = Path(__file__).parents[1] / "src" / "agent_console" / "presentation.py" source = path.read_text(encoding="utf-8") self.assertIn('[data-testid="stAppViewContainer"] li', source) self.assertIn('[data-testid="stExpander"] summary', source) self.assertIn('div[data-testid="stButton"] > button', source) def test_hmm_scenarios_are_enabled_and_unique(self) -> None: path = Path(__file__).parents[1] / "config" / "hmm_demo_scenarios.json" scenarios = load_demo_scenarios(path) self.assertGreaterEqual(len(scenarios), 3) self.assertEqual(len(scenarios), len({item.scenario_id for item in scenarios})) self.assertTrue(all(item.question.strip() for item in scenarios)) def test_hmm_demo_user_presets_reference_runtime_token_only(self) -> None: path = Path(__file__).parents[1] / "config" / "vpd_token_presets.json" payload = json.loads(path.read_text(encoding="utf-8")) presets = payload["presets"] self.assertEqual(payload["version"], 2) self.assertEqual({item["user_id"] for item in presets}, { "E1001", "E1002", "E1003", "E1005", "E1007" }) self.assertTrue(all(item["mcp_token_env"] == "HMM_MCP_BEARER_TOKEN" for item in presets)) self.assertTrue(all("token" not in item for item in presets)) def test_duplicate_id_is_rejected(self) -> None: with tempfile.TemporaryDirectory() as temp_dir: path = Path(temp_dir) / "scenarios.json" path.write_text( json.dumps( { "scenarios": [ {"id": "HR-01", "title": "one", "question": "q1"}, {"id": "HR-01", "title": "two", "question": "q2"}, ] } ), encoding="utf-8", ) with self.assertRaises(ScenarioConfigError): load_demo_scenarios(path) def test_default_mcp_tool_arguments_follow_discovered_query_schema(self) -> None: tool = McpTool( name="search_hr_data", description="", schema={ "type": "object", "properties": {"query": {"type": "string"}}, "required": ["query"], }, read_only=True, ) arguments = build_mcp_tool_arguments( tool, "직원 E1005의 휴가 신청 내역", 50, preferred_tool="search_hr_data" ) self.assertEqual(arguments, {"query": "직원 E1005의 휴가 신청 내역"}) def test_term_tool_arguments_follow_discovered_term_schema(self) -> None: tool = McpTool( name="resolve_hr_term", description="", schema={ "type": "object", "properties": {"term": {"type": "string"}}, "required": ["term"], }, read_only=True, ) arguments = build_mcp_tool_arguments( tool, "반차", 50, preferred_tool="search_hr_data" ) self.assertEqual(arguments, {"term": "반차"}) def test_status_result_policy_text_is_preserved_as_answer_evidence(self) -> None: result = { "status": "success", "result": ( "HR_POLICY_SEARCH_RESULT\n" "EVIDENCE|file=KR_Leave_Policy.pdf|chunk=13|text=이월 기준" ), } summary = status_result_summary(result, excerpt_chars=40) evidence = status_result_evidence(result) self.assertEqual(summary["status"], "success") self.assertGreater(summary["result_chars"], 40) self.assertIn("KR_Leave_Policy.pdf", evidence["result"]) self.assertTrue(has_actionable_text_result(result)) def test_no_data_text_is_not_actionable(self) -> None: self.assertFalse( has_actionable_text_result({"status": "success", "result": "No data found"}) ) @unittest.skipIf(_load_poc4_query_helpers() is None, "Streamlit runtime is optional") def test_policy_query_does_not_include_demo_user_context(self) -> None: prepare = _load_poc4_query_helpers() assert prepare is not None tool = McpTool( name="search_hr_policy", description="Search policy documents", schema={"properties": {"query": {"type": "string"}}}, read_only=True, ) query = prepare( question="연차 휴가 이월 기준과 제한을 알려줘", tool=tool, model_profile_key="gpt54_mini_oci", selected_user_id="E1001", selected_user_role="HR Team Manager", selected_user_team="HMM HR Demo Team", selected_user_scope="팀원 6명 관리", ) self.assertEqual(query, "연차 휴가 이월 기준과 제한을 알려줘") if __name__ == "__main__": unittest.main()