
    Ai                     t    d Z ddlZddlmc mZ ddlZddlm	Z	m
Z
mZ  G d d          Z G d d          ZdS )z
Integration tests for Slack Bot dispatcher.

Tests message dispatch flow with mocked Slack client and LLM,
verifying tool invocation, context management, and response formatting.
    N)	MagicMockpatchcallc                      e Zd ZdZ ed           ed           ed          dedededd	fd
                                    Z ed           ed           ed          dedededd	fd                                    Z ed           ed           ed          dedededd	fd                                    Z ed           ed           ed          dedededd	fd                                    Z	 ed           ed           ed          dedededd	fd                                    Z
d	S )TestDispatcherBasicFlowz5Test core dispatch pipeline with mocked dependencies.slack_bot.dispatcher.WebClientslack_bot.dispatcher.GeminiLLM#slack_bot.dispatcher.ContextStoragemock_storage_clsmock_llm_clsmock_client_clsreturnNc                 :   ddl m} t                      }||_        t                      }||_        d|j        _        t                      }||_        g |j        _         |            }|                    ddd           |j                            dd           d	S )
z4Dispatcher posts LLM text response to Slack channel.r   MessageDispatcher)$   你好！有什么可以帮你的？Nu   你好C123U456r   )channeltextN)	slack_bot.dispatcherr   r   return_valuegenerate_responseget_contextdispatchchat_postMessageassert_called_with)	selfr   r   r   r   mock_clientmock_llmmock_storage
dispatchers	            9/root/projects/butler/tests/integration/test_slack_bot.pytest_simple_text_responsez1TestDispatcherBasicFlow.test_simple_text_response   s     	;:::::kk'2$;;$,!2`"/ {{(4%02 -&&((
Hff555$777 	8 	
 	
 	
 	
 	
    c                 4   ddl m} t                      }||_        d|j        _        t                      }||_        g |j        _        t                      |_         |            }|                    ddd           |j                            dd           dS )	z-User message is persisted to context storage.r   r   OKNu   记录午餐：鸡胸肉C456U789userN)	r   r   r   r   r   r   r   add_messageassert_any_call)r   r   r   r   r   r    r!   r"   s           r#   "test_user_message_saved_to_contextz:TestDispatcherBasicFlow.test_user_message_saved_to_context+   s     	;:::::;;$,!2>"/ {{(4%02 -'0{{$&&((
6GGG 009STTTTTr%   c                    ddl m} t                      }||_        d|j        _        t                      }||_        g |j        _        t                      |_         |            }|                    ddd           d |j        j        D             }d |D             }	t          |	          }
|
sd	d
t          j                    v st          j        t                    rt          j        t                    nd
t          j        |	          t          j        |
          dz  }t          t          j        |                    dx}	}
dS )zBAssistant response is persisted to context storage after dispatch.r   r   )u   已记录！Nu   记录午餐C789U111c                 ,    g | ]}t          |          S  )str.0cs     r#   
<listcomp>zTTestDispatcherBasicFlow.test_assistant_response_saved_to_context.<locals>.<listcomp>Z   s    OOO!s1vvOOOr%   c              3      K   | ]}d |v V  	dS )	assistantNr3   r5   s     r#   	<genexpr>zSTestDispatcherBasicFlow.test_assistant_response_saved_to_context.<locals>.<genexpr>[   s'      99;!#999999r%   z,assert %(py4)s
{%(py4)s = %(py0)s(%(py2)s)
}any)py0py2py4N)r   r   r   r   r   r   r   r,   call_args_listr<   @py_builtinslocals
@pytest_ar_should_repr_global_name	_safereprAssertionError_format_explanation)r   r   r   r   r   r    r!   r"   saved_calls@py_assert1@py_assert3@py_format5s               r#   (test_assistant_response_saved_to_contextz@TestDispatcherBasicFlow.test_assistant_response_saved_to_contextC   sY    	;:::::;;$,!2H"/ {{(4%02 -'0{{$&&((
NFF;;; PO|'?'NOOO99[9999s9999999999999999s99999s999999999999999999999999999999999r%   c                 r   ddl m} t                      }||_        dddid}d|gfdg|j        _        t                      }||_        g |j        _        t                      |_        t          d	dt          d
          i          5   |            }|                    ddd           ddd           n# 1 swxY w Y   |j        	                                 |j        }	|	j
        }
d}|
|k    }|st          j        d|fd|
|f          dt          j                    v st          j        |          rt          j        |          ndt          j        |	          t          j        |
          t          j        |          dz  }dd|iz  }t#          t          j        |                    dx}	x}
x}}dS )z<When LLM returns a tool call, the tool function is executed.r   r   get_daily_detailed_statstarget_datez
2026-03-01)nameargs )u.   昨天睡眠分数 82 分，步数 8000 步。Nz'slack_bot.tools.registry.TOOL_FUNCTIONSzsleep_score: 82, steps: 8000)r   u   昨天健康数据C001U001N   )==)zY%(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.generate_response
}.call_count
} == %(py7)sr    )r=   r>   r?   py7zassert %(py9)spy9)r   r   r   r   r   side_effectr   r   r   assert_called
call_countrC   _call_reprcomparerA   rB   rD   rE   rF   rG   )r   r   r   r   r   r    	tool_callr!   r"   rI   rJ   @py_assert6@py_assert5@py_format8@py_format10s                  r#   test_tool_call_executedz/TestDispatcherBasicFlow.test_tool_call_executed]   sY    	;:::::;;$,! /"L1
 
	 )D2
".
 !{{(4%02 -'0{{$5'@^)_)_)_`
 
 	F 	F +*,,J 4ffEEE	F 	F 	F 	F 	F 	F 	F 	F 	F 	F 	F 	F 	F 	F 	F 	"00222)9)499499999999994999999999999x99999x999999)9999499999999999999999999999999999999999s   "B66B:=B:c                    ddl m} t                      }||_        d|j        _        t                      }||_        g |j        _        t                      }||_         |            }|                    ddd           g }	|j        }
|
j        }|}|s|j	        }|j        }|}|s@ddt          j                    v st          j        |          rt          j        |          ndt          j        |
          t          j        |          d	z  }|	                    |           |sd
dt          j                    v st          j        |          rt          j        |          ndt          j        |          t          j        |          dz  }|	                    |           t          j        |	d          i z  }dd|iz  }t#          t          j        |                    dx}x}	x}
x}x}}dS )z<Empty LLM response triggers a fallback message, not a crash.r   r   )rR   Nu   随便说点什么C002U002zI%(py6)s
{%(py6)s = %(py4)s
{%(py4)s = %(py2)s.chat_postMessage
}.called
}r   )r>   r?   py6zH%(py12)s
{%(py12)s = %(py10)s
{%(py10)s = %(py8)s.chat_update
}.called
})py8py10py12   zassert %(py15)spy15N)r   r   r   r   r   r   r   r   calledchat_updaterA   rB   rC   rD   rE   append_format_booloprF   rG   )r   r   r   r   r   r    r!   r   r"   rI   rJ   r_   @py_assert0@py_assert9@py_assert11@py_format7@py_format13@py_format14@py_format16s                      r#    test_empty_llm_response_fallbackz8TestDispatcherBasicFlow.test_empty_llm_response_fallback   s:    	;:::::;;$,!2<"/ {{(4%02 -kk'2$&&((
0&&AAA 	UT{+T+2T2T2Tk6MT6M6TT6TTTTTTTTTTTTTT{TTTTT{TTTTTT+TTTT2TTTTTTTTTTTTT2TTTTTTTTTTTkTTTTTkTTTTTT6MTTTT6TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTr%   )__name__
__module____qualname____doc__r   r   r$   r.   rL   rb   rw   r3   r%   r#   r   r      s       ??
U+,,
U+,,
U011
 )
9B
U^
	
 
 
 21 -, -,
2 U+,,
U+,,
U011U )U9BUU^U	U U U 21 -, -,U* U+,,
U+,,
U011: ):9B:U^:	: : : 21 -, -,:. U+,,
U+,,
U011#: )#:9B#:U^#:	#: #: #: 21 -, -,#:J U+,,
U+,,
U011U )U9BUU^U	U U U 21 -, -,U U Ur%   r   c                       e Zd ZdZ ed           ed           ed          dedededd	fd
                                    Zd	S )TestContextIsolationz6Test that different channels have independent context.r   r	   r
   r   r   r   r   Nc                    ddl m} t                      }||_        d|j        _        t                      }||_        g |j        _        t                      |_         |            }|                    ddd           |                    ddd           d	 |j        D             }d}	|	|v }
|
st          j	        d
|
fd|	|f          t          j
        |	          dt          j                    v st          j        |          rt          j
        |          nddz  }dd|iz  }t          t          j        |                    dx}	}
d}	|	|v }
|
st          j	        d
|
fd|	|f          t          j
        |	          dt          j                    v st          j        |          rt          j
        |          nddz  }dd|iz  }t          t          j        |                    dx}	}
dS )z5Each channel creates its own ContextStorage instance.r   r   r'   Hello	CHANNEL_ArT   	CHANNEL_Bre   c                 *    g | ]}|d          d          S )r   r3   )r6   	call_argss     r#   r8   zUTestContextIsolation.test_different_channels_get_separate_storage.<locals>.<listcomp>   s     XXX9y|AXXXr%   )in)z%(py1)s in %(py3)schannel_ids)py1py3zassert %(py5)spy5N)r   r   r   r   r   r   r   r@   rC   r\   rE   rA   rB   rD   rF   rG   )r   r   r   r   r   r    r!   r"   r   rp   @py_assert2@py_format4@py_format6s                r#   ,test_different_channels_get_separate_storagezATestContextIsolation.test_different_channels_get_separate_storage   s=    	;:::::;;$,!2>"/ {{(4%02 -'0{{$&&((
G[&999G[&999 YX8H8WXXX){k)))))))))){k))))){)))))))))))k)))))k)))))))))))))))))))))))))))){k)))))))))){k))))){)))))))))))k)))))k)))))))))))))))))))))))))))))r%   )rx   ry   rz   r{   r   r   r   r3   r%   r#   r}   r}      s        @@
U+,,
U+,,
U011* )*9B*U^*	* * * 21 -, -,* * *r%   r}   )r{   builtinsrA   _pytest.assertion.rewrite	assertionrewriterC   pytestunittest.mockr   r   r   r   r}   r3   r%   r#   <module>r      s                  0 0 0 0 0 0 0 0 0 0RU RU RU RU RU RU RU RUj* * * * * * * * * *r%   