from datetime import timezone, timedelta from pathlib import Path def test_sales_menu_uses_injected_latest_date(): from services.openclaw_bot import menu_keyboards menu_keyboards.configure_menu_keyboards( latest_date_provider=lambda: "2026/04/30", goals={}, taipei_tz=timezone(timedelta(hours=8)), ) rows = menu_keyboards._submenu_sales() assert rows[0][0]["callback_data"] == "cmd:sales:2026/04/30" assert rows[0][0]["text"] == "📊 今日 (04/30)" assert rows[0][1]["callback_data"] == "cmd:sales:2026/04/29" assert rows[-1] == menu_keyboards._BACK def test_goal_menu_uses_injected_goal_state(): from services.openclaw_bot import menu_keyboards menu_keyboards.configure_menu_keyboards(goals={"daily": 1_500_000, "monthly": 0}) rows = menu_keyboards._submenu_goals() assert rows[1][0]["text"] == "日目標 (150萬)" assert rows[1][1]["text"] == "月目標 (未設)" def test_category_menu_and_submenu_registry_are_stable(): from services.openclaw_bot import menu_keyboards menu_keyboards.configure_menu_keyboards(latest_date_provider=lambda: "2026/04/30") rows = menu_keyboards._submenu_category() assert rows[0][0]["callback_data"] == "cmd:catdetail:美妝保養:2026/04/30" assert rows[-2] == [{"text": "🗂 全分類清單", "callback_data": "cmd:category:2026/04/30"}] assert rows[-1] == menu_keyboards._BACK assert set(menu_keyboards._SUBMENUS) >= { "main", "sales", "products", "goals", "analysis", "trend", "reports", "market", "competitor", "competitor_ppt", "category", } def test_competitor_menu_keeps_date_input_action(): from services.openclaw_bot import menu_keyboards menu_keyboards.configure_menu_keyboards(latest_date_provider=lambda: "2026/04/30") rows = menu_keyboards._submenu_competitor() assert any("指定日期" in button["text"] and button["callback_data"] == "await:date_competitor" for row in rows for button in row) def test_competitor_ppt_menu_layout_stays_row_based(): from services.openclaw_bot import menu_keyboards rows = menu_keyboards._submenu_competitor_ppt() assert rows[0][0]["text"] == "📆 半年比較" assert rows[0][1]["text"] == "🗓 年比較" assert rows[-1] == menu_keyboards._BACK def test_chunk_rows_respects_row_size(): from services.openclaw_bot import menu_keyboards rows = menu_keyboards._chunk_rows( [ ('A', 'a'), ('B', 'b'), ('C', 'c'), ], row_size=2, ) assert rows == [ [{'text': 'A', 'callback_data': 'a'}, {'text': 'B', 'callback_data': 'b'}], [{'text': 'C', 'callback_data': 'c'}], ] def test_openclaw_routes_import_menu_keyboard_helpers(): route_source = Path("routes/openclaw_bot_routes.py").read_text(encoding="utf-8") assert "from services.openclaw_bot.menu_keyboards import" in route_source assert "configure_menu_keyboards(latest_date_provider=latest_date" in route_source assert "def main_menu_keyboard():" not in route_source def test_quick_menu_keyboard_has_two_column_layout(): from services.openclaw_bot import menu_keyboards menu_keyboards.configure_menu_keyboards() rows = menu_keyboards.quick_menu_keyboard() assert len(rows) == 3 assert all(1 <= len(row) <= 2 for row in rows) assert rows[0][0]['callback_data'].startswith('menu:') def test_polling_telegram_bot_bridges_openclaw_menu_callbacks(): service_source = Path("services/telegram_bot_service.py").read_text(encoding="utf-8") assert 'CommandHandler("menu", self.cmd_menu)' in service_source assert "data.startswith(('menu:', 'cmd:', 'await:'))" in service_source assert "openclaw_waiting_for" in service_source def test_market_info_handlers_accept_text_mcp_contract(): route_source = Path("routes/openclaw_bot_routes.py").read_text(encoding="utf-8") assert "def _send_mcp_text_result" in route_source assert "data = get_upcoming_events()" in route_source assert "get_upcoming_events(60)" not in route_source def test_mcp_collector_has_stable_fallbacks(): source = Path("services/mcp_collector_service.py").read_text(encoding="utf-8") assert "def _fallback_topic_content" in source assert "def _looks_unreliable" in source assert '["google_search"]' in source assert "OllamaService(model=ollama_model).generate" in source assert "return self._fallback_topic_content" in source def test_polling_menu_imports_openclaw_routes_for_runtime_configuration(): service_source = Path("services/telegram_bot_service.py").read_text(encoding="utf-8") assert "from routes import openclaw_bot_routes as openclaw" in service_source assert "openclaw.main_menu_keyboard()" in service_source assert "openclaw._SUBMENUS.get(key)" in service_source def test_help_keyboard_uses_reusable_quick_menu(): route_source = Path("routes/openclaw_bot_routes.py").read_text(encoding="utf-8") assert "help_kb = quick_menu_keyboard()" in route_source