""" Tests for FAZA 3 - New Command Handlers with Button Interface Tests for menu_command, trezorerie_casa_command, trezorerie_banca_command, clienti_command, furnizori_command, evolutie_command and modifications to existing commands. """ import pytest from unittest.mock import AsyncMock, MagicMock, patch from telegram import Update, User, Message from telegram.ext import ContextTypes from app.bot.handlers import ( menu_command, trezorerie_casa_command, trezorerie_banca_command, clienti_command, furnizori_command, evolutie_command, start_command, dashboard_command, facturi_command, trezorerie_command ) @pytest.fixture def mock_update(): """Create mock Update object""" update = MagicMock(spec=Update) update.effective_user = MagicMock(spec=User) update.effective_user.id = 12345 update.effective_user.username = "testuser" update.message = MagicMock(spec=Message) update.message.reply_text = AsyncMock() return update @pytest.fixture def mock_context(): """Create mock Context object""" context = MagicMock(spec=ContextTypes.DEFAULT_TYPE) context.args = [] return context # ============================================================================= # TEST: menu_command # ============================================================================= @pytest.mark.asyncio async def test_menu_command_linked_user(mock_update, mock_context): """Test /menu for linked user with active company""" with patch('app.bot.handlers.check_user_linked', new_callable=AsyncMock) as mock_check: mock_check.return_value = True with patch('app.bot.handlers.get_session_manager') as mock_session: # Mock session with active company mock_session_obj = MagicMock() mock_session_obj.get_active_company.return_value = { 'id': 1, 'name': 'Test Co', 'cui': '12345' } mock_session.return_value.get_or_create_session = AsyncMock(return_value=mock_session_obj) await menu_command(mock_update, mock_context) # Verify message was sent with keyboard assert mock_update.message.reply_text.called call_kwargs = mock_update.message.reply_text.call_args.kwargs assert 'reply_markup' in call_kwargs assert call_kwargs['reply_markup'] is not None @pytest.mark.asyncio async def test_menu_command_unlinked_user(mock_update, mock_context): """Test /menu for unlinked user""" with patch('app.bot.handlers.check_user_linked', new_callable=AsyncMock) as mock_check: mock_check.return_value = False await menu_command(mock_update, mock_context) # Verify error message was sent assert mock_update.message.reply_text.called call_args = mock_update.message.reply_text.call_args.args # Message now says "Cont neconectat" (not "nelinkuit") assert "neconectat" in call_args[0].lower() or "start" in call_args[0].lower() @pytest.mark.asyncio async def test_menu_command_no_company(mock_update, mock_context): """Test /menu when no company is selected""" with patch('app.bot.handlers.check_user_linked', new_callable=AsyncMock) as mock_check: mock_check.return_value = True with patch('app.bot.handlers.get_session_manager') as mock_session: # Mock session with NO active company mock_session_obj = MagicMock() mock_session_obj.get_active_company.return_value = None mock_session.return_value.get_or_create_session = AsyncMock(return_value=mock_session_obj) await menu_command(mock_update, mock_context) # Verify menu was still shown (with company selection button) assert mock_update.message.reply_text.called call_kwargs = mock_update.message.reply_text.call_args.kwargs assert 'reply_markup' in call_kwargs # ============================================================================= # TEST: trezorerie_casa_command # ============================================================================= @pytest.mark.asyncio async def test_trezorerie_casa_command(mock_update, mock_context): """Test /trezorerie_casa command""" with patch('app.bot.handlers.check_user_linked', new_callable=AsyncMock, return_value=True): with patch('app.bot.helpers.get_active_company_or_prompt', new_callable=AsyncMock) as mock_company: mock_company.return_value = {'id': 1, 'name': 'Test Co'} with patch('app.bot.handlers.get_user_auth_data', new_callable=AsyncMock) as mock_auth: mock_auth.return_value = {'jwt_token': 'fake_token'} with patch('app.bot.helpers.get_treasury_breakdown_split', new_callable=AsyncMock) as mock_treasury: mock_treasury.return_value = { 'casa': {'accounts': [], 'total': 5000}, 'banca': {'accounts': [], 'total': 10000} } await trezorerie_casa_command(mock_update, mock_context) # Verify message was sent with keyboard assert mock_update.message.reply_text.called call_kwargs = mock_update.message.reply_text.call_args.kwargs assert 'reply_markup' in call_kwargs assert call_kwargs['reply_markup'] is not None @pytest.mark.asyncio async def test_trezorerie_casa_unlinked_user(mock_update, mock_context): """Test /trezorerie_casa for unlinked user""" with patch('app.bot.handlers.check_user_linked', new_callable=AsyncMock, return_value=False): await trezorerie_casa_command(mock_update, mock_context) # Verify error message assert mock_update.message.reply_text.called call_args = mock_update.message.reply_text.call_args.args # Message now says "Cont neconectat" (not "nelinkuit") assert "neconectat" in call_args[0].lower() or "start" in call_args[0].lower() # ============================================================================= # TEST: trezorerie_banca_command # ============================================================================= @pytest.mark.asyncio async def test_trezorerie_banca_command(mock_update, mock_context): """Test /trezorerie_banca command""" with patch('app.bot.handlers.check_user_linked', new_callable=AsyncMock, return_value=True): with patch('app.bot.helpers.get_active_company_or_prompt', new_callable=AsyncMock) as mock_company: mock_company.return_value = {'id': 1, 'name': 'Test Co'} with patch('app.bot.handlers.get_user_auth_data', new_callable=AsyncMock) as mock_auth: mock_auth.return_value = {'jwt_token': 'fake_token'} with patch('app.bot.helpers.get_treasury_breakdown_split', new_callable=AsyncMock) as mock_treasury: mock_treasury.return_value = { 'casa': {'accounts': [], 'total': 5000}, 'banca': {'accounts': [], 'total': 10000} } await trezorerie_banca_command(mock_update, mock_context) # Verify message was sent with keyboard assert mock_update.message.reply_text.called call_kwargs = mock_update.message.reply_text.call_args.kwargs assert 'reply_markup' in call_kwargs # ============================================================================= # TEST: clienti_command # ============================================================================= @pytest.mark.asyncio async def test_clienti_command(mock_update, mock_context): """Test /clienti command""" with patch('app.bot.handlers.check_user_linked', new_callable=AsyncMock, return_value=True): with patch('app.bot.helpers.get_active_company_or_prompt', new_callable=AsyncMock) as mock_company: mock_company.return_value = {'id': 1, 'name': 'Test Co'} with patch('app.bot.handlers.get_user_auth_data', new_callable=AsyncMock) as mock_auth: mock_auth.return_value = {'jwt_token': 'fake_token'} with patch('app.bot.helpers.get_clients_with_maturity', new_callable=AsyncMock) as mock_clients: mock_clients.return_value = { 'clients': [{'id': 1, 'name': 'Client A', 'balance': 5000}], 'maturity': {'in_term': 3000, 'overdue': 2000, 'total': 5000} } await clienti_command(mock_update, mock_context) # Verify message was sent with client list keyboard assert mock_update.message.reply_text.called call_kwargs = mock_update.message.reply_text.call_args.kwargs assert 'reply_markup' in call_kwargs @pytest.mark.asyncio async def test_clienti_command_no_data(mock_update, mock_context): """Test /clienti command when API returns no data""" with patch('app.bot.handlers.check_user_linked', new_callable=AsyncMock, return_value=True): with patch('app.bot.helpers.get_active_company_or_prompt', new_callable=AsyncMock) as mock_company: mock_company.return_value = {'id': 1, 'name': 'Test Co'} with patch('app.bot.handlers.get_user_auth_data', new_callable=AsyncMock) as mock_auth: mock_auth.return_value = {'jwt_token': 'fake_token'} with patch('app.bot.helpers.get_clients_with_maturity', new_callable=AsyncMock) as mock_clients: mock_clients.return_value = None await clienti_command(mock_update, mock_context) # Verify error message assert mock_update.message.reply_text.called call_args = mock_update.message.reply_text.call_args.args assert "Eroare" in call_args[0] # ============================================================================= # TEST: furnizori_command # ============================================================================= @pytest.mark.asyncio async def test_furnizori_command(mock_update, mock_context): """Test /furnizori command""" with patch('app.bot.handlers.check_user_linked', new_callable=AsyncMock, return_value=True): with patch('app.bot.helpers.get_active_company_or_prompt', new_callable=AsyncMock) as mock_company: mock_company.return_value = {'id': 1, 'name': 'Test Co'} with patch('app.bot.handlers.get_user_auth_data', new_callable=AsyncMock) as mock_auth: mock_auth.return_value = {'jwt_token': 'fake_token'} with patch('app.bot.helpers.get_suppliers_with_maturity', new_callable=AsyncMock) as mock_suppliers: mock_suppliers.return_value = { 'suppliers': [{'id': 1, 'name': 'Supplier A', 'balance': 5000}], 'maturity': {'in_term': 4000, 'overdue': 1000, 'total': 5000} } await furnizori_command(mock_update, mock_context) # Verify message was sent with supplier list keyboard assert mock_update.message.reply_text.called call_kwargs = mock_update.message.reply_text.call_args.kwargs assert 'reply_markup' in call_kwargs # ============================================================================= # TEST: evolutie_command # ============================================================================= @pytest.mark.asyncio async def test_evolutie_command(mock_update, mock_context): """Test /evolutie command""" with patch('app.bot.handlers.check_user_linked', new_callable=AsyncMock, return_value=True): with patch('app.bot.helpers.get_active_company_or_prompt', new_callable=AsyncMock) as mock_company: mock_company.return_value = {'id': 1, 'name': 'Test Co'} with patch('app.bot.handlers.get_user_auth_data', new_callable=AsyncMock) as mock_auth: mock_auth.return_value = {'jwt_token': 'fake_token'} with patch('app.bot.helpers.get_cashflow_evolution_data', new_callable=AsyncMock) as mock_evolution: mock_evolution.return_value = { 'performance': {'incasari_total': 100000, 'plati_total': 80000}, 'monthly': {'months': ['Ian', 'Feb'], 'incasari': [50000, 50000]} } await evolutie_command(mock_update, mock_context) # Verify message was sent with action buttons (no export) assert mock_update.message.reply_text.called call_kwargs = mock_update.message.reply_text.call_args.kwargs assert 'reply_markup' in call_kwargs # ============================================================================= # TEST: Modified existing commands # ============================================================================= @pytest.mark.asyncio async def test_start_command_linked_shows_menu(mock_update, mock_context): """Test that /start shows menu for linked users""" with patch('app.bot.handlers.check_user_linked', new_callable=AsyncMock) as mock_check: mock_check.return_value = True with patch('app.bot.handlers.get_user_auth_data', new_callable=AsyncMock) as mock_auth: mock_auth.return_value = {'username': 'testuser', 'jwt_token': 'fake_token'} with patch('app.bot.handlers.get_session_manager') as mock_session: mock_session_obj = MagicMock() mock_session_obj.get_active_company.return_value = {'id': 1, 'name': 'Test Co'} mock_session.return_value.get_or_create_session = AsyncMock(return_value=mock_session_obj) await start_command(mock_update, mock_context) # Verify menu keyboard was sent assert mock_update.message.reply_text.called call_kwargs = mock_update.message.reply_text.call_args.kwargs assert 'reply_markup' in call_kwargs assert call_kwargs['reply_markup'] is not None @pytest.mark.asyncio async def test_dashboard_command_has_action_buttons(mock_update, mock_context): """Test that /dashboard command now includes action buttons""" with patch('app.bot.handlers.check_user_linked', new_callable=AsyncMock, return_value=True): with patch('app.bot.helpers.get_active_company_or_prompt', new_callable=AsyncMock) as mock_company: mock_company.return_value = {'id': 1, 'name': 'Test Co'} with patch('app.bot.handlers.get_user_auth_data', new_callable=AsyncMock) as mock_auth: mock_auth.return_value = {'jwt_token': 'fake_token'} # Mock backend client mock_backend_client = MagicMock() mock_backend_client.__aenter__ = AsyncMock(return_value=mock_backend_client) mock_backend_client.__aexit__ = AsyncMock() mock_backend_client.get_dashboard_data = AsyncMock(return_value={ 'sold_total': 10000, 'facturi_emise': 10, 'facturi_platite': 5 }) with patch('app.bot.handlers.get_backend_client', return_value=mock_backend_client): await dashboard_command(mock_update, mock_context) # Verify action buttons were added assert mock_update.message.reply_text.called call_kwargs = mock_update.message.reply_text.call_args.kwargs assert 'reply_markup' in call_kwargs assert call_kwargs['reply_markup'] is not None @pytest.mark.asyncio async def test_facturi_command_has_action_buttons(mock_update, mock_context): """Test that /facturi command now includes action buttons""" with patch('app.bot.handlers.check_user_linked', new_callable=AsyncMock, return_value=True): with patch('app.bot.helpers.get_active_company_or_prompt', new_callable=AsyncMock) as mock_company: mock_company.return_value = {'id': 1, 'name': 'Test Co'} with patch('app.bot.handlers.get_user_auth_data', new_callable=AsyncMock) as mock_auth: mock_auth.return_value = {'jwt_token': 'fake_token'} # Mock backend client mock_backend_client = MagicMock() mock_backend_client.__aenter__ = AsyncMock(return_value=mock_backend_client) mock_backend_client.__aexit__ = AsyncMock() mock_backend_client.search_invoices = AsyncMock(return_value=[ {'id': 1, 'number': 'FV001', 'amount': 5000} ]) with patch('app.bot.handlers.get_backend_client', return_value=mock_backend_client): await facturi_command(mock_update, mock_context) # Verify action buttons were added assert mock_update.message.reply_text.called call_kwargs = mock_update.message.reply_text.call_args.kwargs assert 'reply_markup' in call_kwargs @pytest.mark.asyncio async def test_trezorerie_command_has_action_buttons(mock_update, mock_context): """Test that /trezorerie command now includes action buttons""" with patch('app.bot.handlers.check_user_linked', new_callable=AsyncMock, return_value=True): with patch('app.bot.handlers.get_session_manager') as mock_session: mock_session_obj = MagicMock() mock_session_obj.get_active_company.return_value = {'id': 1, 'name': 'Test Co'} mock_session.return_value.get_or_create_session = AsyncMock(return_value=mock_session_obj) with patch('app.bot.handlers.get_user_auth_data', new_callable=AsyncMock) as mock_auth: mock_auth.return_value = {'jwt_token': 'fake_token'} # Mock backend client mock_backend_client = MagicMock() mock_backend_client.__aenter__ = AsyncMock(return_value=mock_backend_client) mock_backend_client.__aexit__ = AsyncMock() mock_backend_client.get_treasury_data = AsyncMock(return_value={ 'cash_total': 5000, 'bank_total': 10000 }) with patch('app.bot.handlers.get_backend_client', return_value=mock_backend_client): await trezorerie_command(mock_update, mock_context) # Verify message was sent (may or may not have action buttons depending on implementation) assert mock_update.message.reply_text.called