""" Tests for approval_handler module - ApprovalHandler and ApprovalState classes. TDD Task 1: Test scaffold for approval workflow handling. """ import pytest from unittest.mock import Mock, MagicMock import time # These imports will fail initially (RED phase) from gsd_openclaw.response_formatter import FeishuResponseFormatter, CardButton class TestApprovalHandlerDetection: """Test approval request detection from output.""" def test_detect_approval_request_finds_ask_user_question(self): """detect_approval_request returns context for 'AskUserQuestion' output.""" from gsd_openclaw.approval_handler import ApprovalHandler formatter = Mock(spec=FeishuResponseFormatter) handler = ApprovalHandler(formatter) output = "Some text\nAskUserQuestion: Do you want to proceed?\nMore text" result = handler.detect_approval_request(output) assert result is not None assert "AskUserQuestion" in result.get("type", "") def test_detect_approval_request_finds_chinese_confirm(self): """detect_approval_request returns context for '需要确认' output.""" from gsd_openclaw.approval_handler import ApprovalHandler formatter = Mock(spec=FeishuResponseFormatter) handler = ApprovalHandler(formatter) output = "正在执行...\n需要确认: 是否继续?\n请回复" result = handler.detect_approval_request(output) assert result is not None assert "需要确认" in result.get("type", "") class TestApprovalHandlerCardCreation: """Test approval card creation with buttons.""" def test_create_approval_card_has_confirm_title(self): """create_approval_card has title '需要确认'.""" from gsd_openclaw.approval_handler import ApprovalHandler formatter = Mock(spec=FeishuResponseFormatter) formatter.format_info = Mock(return_value={ "schema": "2.0", "header": {"title": {"content": "需要确认"}, "template": "blue"}, "elements": [] }) handler = ApprovalHandler(formatter) context = {"question": "是否继续?"} card = handler.create_approval_card(context, "session-123", "test-project") formatter.format_info.assert_called_once() call_args = formatter.format_info.call_args assert call_args[0][0] == "需要确认" def test_create_approval_card_has_confirm_button(self): """create_approval_card has confirm button with action='approve'.""" from gsd_openclaw.approval_handler import ApprovalHandler formatter = Mock(spec=FeishuResponseFormatter) formatter.format_info = Mock(return_value={ "schema": "2.0", "header": {"title": {"content": "需要确认"}, "template": "blue"}, "elements": [] }) handler = ApprovalHandler(formatter) context = {"question": "是否继续?"} card = handler.create_approval_card(context, "session-123", "test-project") # Check that format_info was called with buttons call_args = formatter.format_info.call_args buttons = call_args[1].get('buttons') or (call_args[0][2] if len(call_args[0]) > 2 else None) assert buttons is not None # Find confirm button confirm_btn = next((b for b in buttons if b.value.get("action") == "approve"), None) assert confirm_btn is not None assert confirm_btn.button_type == "primary" def test_create_approval_card_has_reject_button(self): """create_approval_card has reject button with action='reject'.""" from gsd_openclaw.approval_handler import ApprovalHandler formatter = Mock(spec=FeishuResponseFormatter) formatter.format_info = Mock(return_value={ "schema": "2.0", "header": {"title": {"content": "需要确认"}, "template": "blue"}, "elements": [] }) handler = ApprovalHandler(formatter) context = {"question": "是否继续?"} card = handler.create_approval_card(context, "session-123", "test-project") # Check that format_info was called with buttons call_args = formatter.format_info.call_args buttons = call_args[1].get('buttons') or (call_args[0][2] if len(call_args[0]) > 2 else None) assert buttons is not None # Find reject button reject_btn = next((b for b in buttons if b.value.get("action") == "reject"), None) assert reject_btn is not None assert reject_btn.button_type == "danger" class TestApprovalHandlerCallback: """Test approval callback handling.""" def test_handle_callback_returns_approved_for_approve_action(self): """handle_callback returns 'approved' for approve action.""" from gsd_openclaw.approval_handler import ApprovalHandler, ApprovalState formatter = Mock(spec=FeishuResponseFormatter) handler = ApprovalHandler(formatter) # Create a pending approval first context = {"question": "是否继续?"} handler.create_approval_card(context, "session-123", "test-project") # Get the approval_id from pending_approvals approval_id = list(handler.pending_approvals.keys())[0] callback_data = { "action": "approve", "approval_id": approval_id, "project": "test-project" } result = handler.handle_callback(callback_data) assert result == "approved" def test_handle_callback_returns_rejected_for_reject_action(self): """handle_callback returns 'rejected' for reject action.""" from gsd_openclaw.approval_handler import ApprovalHandler formatter = Mock(spec=FeishuResponseFormatter) handler = ApprovalHandler(formatter) # Create a pending approval first context = {"question": "是否继续?"} handler.create_approval_card(context, "session-123", "test-project") # Get the approval_id from pending_approvals approval_id = list(handler.pending_approvals.keys())[0] callback_data = { "action": "reject", "approval_id": approval_id, "project": "test-project" } result = handler.handle_callback(callback_data) assert result == "rejected" def test_handle_callback_returns_error_for_unknown_approval_id(self): """handle_callback returns error for unknown approval_id.""" from gsd_openclaw.approval_handler import ApprovalHandler formatter = Mock(spec=FeishuResponseFormatter) handler = ApprovalHandler(formatter) callback_data = { "action": "approve", "approval_id": "nonexistent-id", "project": "test-project" } result = handler.handle_callback(callback_data) assert "error" in result.lower()