This commit addresses the overly restrictive .gitignore pattern that was excluding all test files (test_*.py), including legitimate pytest and unittest test suites essential for code quality and CI/CD. Changes to .gitignore: - Added negation patterns !**/tests/test_*.py and !**/test_*.py to allow proper test files while still blocking temporary scripts - This enables pytest test suites to be tracked by git Added test files (17 files): Telegram Bot Tests (15 files): - reports-app/telegram-bot/tests/test_auth.py Tests for authentication and account linking flow - reports-app/telegram-bot/tests/test_callbacks.py Tests for callback query handlers - reports-app/telegram-bot/tests/test_formatters.py Tests for message formatting utilities - reports-app/telegram-bot/tests/test_formatters_extended.py Extended formatter tests - reports-app/telegram-bot/tests/test_handlers_menu.py Tests for menu handlers - reports-app/telegram-bot/tests/test_helpers.py Tests for helper functions - reports-app/telegram-bot/tests/test_helpers_extended.py Extended helper tests - reports-app/telegram-bot/tests/test_helpers_real.py Real integration tests for helpers - reports-app/telegram-bot/tests/test_helpers_real_simple.py Simplified integration tests - reports-app/telegram-bot/tests/test_login_flow.py Complete login flow integration tests - reports-app/telegram-bot/tests/test_menus.py Menu system tests - reports-app/telegram-bot/tests/test_session_company.py Session and company management tests - reports-app/telegram-bot/test_claude_integration.py Manual integration test (Claude AI) - reports-app/telegram-bot/test_claude_response.py Response formatting test - reports-app/telegram-bot/test_db.py Database operations manual test Shared Module Tests (2 files): - shared/auth/test_auth.py Authentication system tests - shared/database/test_pool.py Oracle connection pool tests Security verification: ✅ All test files use mock objects, fixtures, and environment variables ✅ No hardcoded credentials or secrets found ✅ Safe for version control 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
358 lines
14 KiB
Python
358 lines
14 KiB
Python
"""
|
|
Tests for FAZA 2 extended helper functions.
|
|
Tests new helpers for treasury breakdown, clients/suppliers with maturity, and invoices.
|
|
"""
|
|
|
|
import pytest
|
|
from unittest.mock import AsyncMock, patch, MagicMock
|
|
from app.bot.helpers import (
|
|
get_treasury_breakdown_split,
|
|
get_clients_with_maturity,
|
|
get_suppliers_with_maturity,
|
|
get_cashflow_evolution_data,
|
|
get_client_invoices,
|
|
get_supplier_invoices
|
|
)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_treasury_breakdown_split():
|
|
"""Test split trezorerie în casa/banca"""
|
|
mock_response = {
|
|
'accounts': [
|
|
{'name': 'Casa Ron', 'type': 'Casa', 'balance': 5000},
|
|
{'name': 'Casa Valuta', 'type': 'Casa', 'balance': 2000},
|
|
{'name': 'BCR', 'type': 'Banca', 'balance': 10000},
|
|
{'name': 'BRD', 'type': 'Banca', 'balance': 5000}
|
|
]
|
|
}
|
|
|
|
with patch('app.bot.helpers.get_backend_client') as mock_client_getter:
|
|
mock_client = MagicMock()
|
|
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
|
mock_client.__aexit__ = AsyncMock(return_value=None)
|
|
mock_client.get_treasury_breakdown = AsyncMock(return_value=mock_response)
|
|
mock_client_getter.return_value = mock_client
|
|
|
|
result = await get_treasury_breakdown_split(1, "fake_token")
|
|
|
|
assert result is not None
|
|
assert 'casa' in result
|
|
assert 'banca' in result
|
|
assert result['casa']['total'] == 7000 # 5000 + 2000
|
|
assert result['banca']['total'] == 15000 # 10000 + 5000
|
|
assert len(result['casa']['accounts']) == 2
|
|
assert len(result['banca']['accounts']) == 2
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_treasury_breakdown_split_casa_in_name():
|
|
"""Test că identifică casa după nume (nu doar după type)"""
|
|
mock_response = {
|
|
'accounts': [
|
|
{'name': 'Casa principala', 'type': 'Gest', 'balance': 3000},
|
|
{'name': 'Cont BCR', 'type': 'Gest', 'balance': 10000}
|
|
]
|
|
}
|
|
|
|
with patch('app.bot.helpers.get_backend_client') as mock_client_getter:
|
|
mock_client = MagicMock()
|
|
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
|
mock_client.__aexit__ = AsyncMock(return_value=None)
|
|
mock_client.get_treasury_breakdown = AsyncMock(return_value=mock_response)
|
|
mock_client_getter.return_value = mock_client
|
|
|
|
result = await get_treasury_breakdown_split(1, "fake_token")
|
|
|
|
assert result['casa']['total'] == 3000 # Casa identificată după nume
|
|
assert result['banca']['total'] == 10000
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_treasury_breakdown_split_api_error():
|
|
"""Test comportament când API returnează None"""
|
|
with patch('app.bot.helpers.get_backend_client') as mock_client_getter:
|
|
mock_client = MagicMock()
|
|
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
|
mock_client.__aexit__ = AsyncMock(return_value=None)
|
|
mock_client.get_treasury_breakdown = AsyncMock(return_value=None)
|
|
mock_client_getter.return_value = mock_client
|
|
|
|
result = await get_treasury_breakdown_split(1, "fake_token")
|
|
|
|
assert result is None
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_clients_with_maturity():
|
|
"""Test obținere clienți cu scadențe"""
|
|
mock_clients_data = {
|
|
'clients': [
|
|
{'id': 1, 'name': 'Client A', 'balance': 15000},
|
|
{'id': 2, 'name': 'Client B', 'balance': 8500}
|
|
]
|
|
}
|
|
mock_maturity_data = {
|
|
'in_term': 18000,
|
|
'overdue': 5500,
|
|
'total': 23500
|
|
}
|
|
|
|
with patch('app.bot.helpers.get_backend_client') as mock_client_getter:
|
|
mock_client = MagicMock()
|
|
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
|
mock_client.__aexit__ = AsyncMock(return_value=None)
|
|
mock_client.get_detailed_data = AsyncMock(return_value=mock_clients_data)
|
|
mock_client.get_maturity_data = AsyncMock(return_value=mock_maturity_data)
|
|
mock_client_getter.return_value = mock_client
|
|
|
|
result = await get_clients_with_maturity(1, "fake_token")
|
|
|
|
assert result is not None
|
|
assert 'clients' in result
|
|
assert 'maturity' in result
|
|
assert len(result['clients']) == 2
|
|
assert result['maturity']['in_term'] == 18000
|
|
assert result['maturity']['overdue'] == 5500
|
|
assert result['maturity']['total'] == 23500
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_clients_with_maturity_items_key():
|
|
"""Test că acceptă atât 'clients' cât și 'items' ca key"""
|
|
mock_clients_data = {
|
|
'items': [
|
|
{'id': 1, 'name': 'Client A', 'balance': 15000}
|
|
]
|
|
}
|
|
mock_maturity_data = {'in_term': 15000, 'overdue': 0, 'total': 15000}
|
|
|
|
with patch('app.bot.helpers.get_backend_client') as mock_client_getter:
|
|
mock_client = MagicMock()
|
|
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
|
mock_client.__aexit__ = AsyncMock(return_value=None)
|
|
mock_client.get_detailed_data = AsyncMock(return_value=mock_clients_data)
|
|
mock_client.get_maturity_data = AsyncMock(return_value=mock_maturity_data)
|
|
mock_client_getter.return_value = mock_client
|
|
|
|
result = await get_clients_with_maturity(1, "fake_token")
|
|
|
|
assert len(result['clients']) == 1
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_clients_with_maturity_no_maturity_data():
|
|
"""Test fallback când maturity data e None"""
|
|
mock_clients_data = {
|
|
'clients': [{'id': 1, 'name': 'Client A', 'balance': 15000}]
|
|
}
|
|
|
|
with patch('app.bot.helpers.get_backend_client') as mock_client_getter:
|
|
mock_client = MagicMock()
|
|
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
|
mock_client.__aexit__ = AsyncMock(return_value=None)
|
|
mock_client.get_detailed_data = AsyncMock(return_value=mock_clients_data)
|
|
mock_client.get_maturity_data = AsyncMock(return_value=None)
|
|
mock_client_getter.return_value = mock_client
|
|
|
|
result = await get_clients_with_maturity(1, "fake_token")
|
|
|
|
assert result['maturity'] == {'in_term': 0, 'overdue': 0, 'total': 0}
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_suppliers_with_maturity():
|
|
"""Test obținere furnizori cu scadențe"""
|
|
mock_suppliers_data = {
|
|
'suppliers': [
|
|
{'id': 1, 'name': 'Supplier A', 'balance': 5000}
|
|
]
|
|
}
|
|
mock_maturity_data = {
|
|
'in_term': 4000,
|
|
'overdue': 1000,
|
|
'total': 5000
|
|
}
|
|
|
|
with patch('app.bot.helpers.get_backend_client') as mock_client_getter:
|
|
mock_client = MagicMock()
|
|
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
|
mock_client.__aexit__ = AsyncMock(return_value=None)
|
|
mock_client.get_detailed_data = AsyncMock(return_value=mock_suppliers_data)
|
|
mock_client.get_maturity_data = AsyncMock(return_value=mock_maturity_data)
|
|
mock_client_getter.return_value = mock_client
|
|
|
|
result = await get_suppliers_with_maturity(1, "fake_token")
|
|
|
|
assert result is not None
|
|
assert 'suppliers' in result
|
|
assert 'maturity' in result
|
|
assert len(result['suppliers']) == 1
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_cashflow_evolution_data():
|
|
"""Test obținere date evoluție cash flow"""
|
|
mock_performance = {
|
|
'incasari_total': 100000,
|
|
'plati_total': 80000,
|
|
'net': 20000
|
|
}
|
|
mock_monthly = {
|
|
'months': ['Ian', 'Feb', 'Mar'],
|
|
'incasari': [30000, 35000, 35000],
|
|
'plati': [25000, 27000, 28000]
|
|
}
|
|
|
|
with patch('app.bot.helpers.get_backend_client') as mock_client_getter:
|
|
mock_client = MagicMock()
|
|
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
|
mock_client.__aexit__ = AsyncMock(return_value=None)
|
|
mock_client.get_performance_data = AsyncMock(return_value=mock_performance)
|
|
mock_client.get_monthly_flows = AsyncMock(return_value=mock_monthly)
|
|
mock_client_getter.return_value = mock_client
|
|
|
|
result = await get_cashflow_evolution_data(1, "fake_token")
|
|
|
|
assert result is not None
|
|
assert 'performance' in result
|
|
assert 'monthly' in result
|
|
assert result['performance']['net'] == 20000
|
|
assert len(result['monthly']['months']) == 3
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_cashflow_evolution_data_no_monthly():
|
|
"""Test fallback când monthly data e None"""
|
|
mock_performance = {
|
|
'incasari_total': 100000,
|
|
'plati_total': 80000,
|
|
'net': 20000
|
|
}
|
|
|
|
with patch('app.bot.helpers.get_backend_client') as mock_client_getter:
|
|
mock_client = MagicMock()
|
|
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
|
mock_client.__aexit__ = AsyncMock(return_value=None)
|
|
mock_client.get_performance_data = AsyncMock(return_value=mock_performance)
|
|
mock_client.get_monthly_flows = AsyncMock(return_value=None)
|
|
mock_client_getter.return_value = mock_client
|
|
|
|
result = await get_cashflow_evolution_data(1, "fake_token")
|
|
|
|
assert result['monthly'] == {'months': [], 'incasari': [], 'plati': []}
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_client_invoices():
|
|
"""Test obținere facturi client"""
|
|
mock_invoices = [
|
|
{'id': 1, 'number': 'FV001', 'amount': 5000, 'status': 'unpaid'},
|
|
{'id': 2, 'number': 'FV002', 'amount': 3500, 'status': 'paid'}
|
|
]
|
|
|
|
with patch('app.bot.helpers.get_backend_client') as mock_client_getter:
|
|
mock_client = MagicMock()
|
|
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
|
mock_client.__aexit__ = AsyncMock(return_value=None)
|
|
mock_client.search_invoices = AsyncMock(return_value=mock_invoices)
|
|
mock_client_getter.return_value = mock_client
|
|
|
|
result = await get_client_invoices(1, "Client A", "fake_token")
|
|
|
|
assert len(result) == 2
|
|
assert result[0]['number'] == 'FV001'
|
|
|
|
# Verifică că a fost apelat cu filtrul corect
|
|
mock_client.search_invoices.assert_called_once()
|
|
call_args = mock_client.search_invoices.call_args
|
|
assert call_args[1]['filters']['partner_type'] == 'CLIENTI'
|
|
assert call_args[1]['filters']['partner_name'] == 'Client A'
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_supplier_invoices():
|
|
"""Test obținere facturi furnizor"""
|
|
mock_invoices = [
|
|
{'id': 1, 'number': 'FC001', 'amount': 2000, 'status': 'unpaid'}
|
|
]
|
|
|
|
with patch('app.bot.helpers.get_backend_client') as mock_client_getter:
|
|
mock_client = MagicMock()
|
|
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
|
mock_client.__aexit__ = AsyncMock(return_value=None)
|
|
mock_client.search_invoices = AsyncMock(return_value=mock_invoices)
|
|
mock_client_getter.return_value = mock_client
|
|
|
|
result = await get_supplier_invoices(1, "Supplier A", "fake_token")
|
|
|
|
assert len(result) == 1
|
|
assert result[0]['number'] == 'FC001'
|
|
|
|
# Verifică că a fost apelat cu filtrul corect
|
|
mock_client.search_invoices.assert_called_once()
|
|
call_args = mock_client.search_invoices.call_args
|
|
assert call_args[1]['filters']['partner_type'] == 'FURNIZORI'
|
|
assert call_args[1]['filters']['partner_name'] == 'Supplier A'
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_client_invoices_empty():
|
|
"""Test comportament când nu există facturi"""
|
|
with patch('app.bot.helpers.get_backend_client') as mock_client_getter:
|
|
mock_client = MagicMock()
|
|
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
|
mock_client.__aexit__ = AsyncMock(return_value=None)
|
|
mock_client.search_invoices = AsyncMock(return_value=[])
|
|
mock_client_getter.return_value = mock_client
|
|
|
|
result = await get_client_invoices(1, "Client A", "fake_token")
|
|
|
|
assert result == []
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_client_invoices_error_handling():
|
|
"""Test error handling pentru get_client_invoices"""
|
|
with patch('app.bot.helpers.get_backend_client') as mock_client_getter:
|
|
mock_client = MagicMock()
|
|
mock_client.__aenter__ = AsyncMock(side_effect=Exception("API Error"))
|
|
mock_client_getter.return_value = mock_client
|
|
|
|
result = await get_client_invoices(1, "Client A", "fake_token")
|
|
|
|
assert result == [] # Returnează listă goală la eroare
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_helpers_handle_none_responses():
|
|
"""Test că helper-ii handle-uiesc răspunsuri None de la API"""
|
|
with patch('app.bot.helpers.get_backend_client') as mock_client_getter:
|
|
mock_client = MagicMock()
|
|
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
|
mock_client.__aexit__ = AsyncMock(return_value=None)
|
|
|
|
# Test toate funcțiile cu None responses
|
|
mock_client.get_detailed_data = AsyncMock(return_value=None)
|
|
mock_client.get_maturity_data = AsyncMock(return_value=None)
|
|
mock_client.get_performance_data = AsyncMock(return_value=None)
|
|
mock_client.get_monthly_flows = AsyncMock(return_value=None)
|
|
mock_client.search_invoices = AsyncMock(return_value=None)
|
|
mock_client_getter.return_value = mock_client
|
|
|
|
# Niciunul nu ar trebui să crapeze
|
|
result1 = await get_clients_with_maturity(1, "token")
|
|
assert result1 is None
|
|
|
|
result2 = await get_suppliers_with_maturity(1, "token")
|
|
assert result2 is None
|
|
|
|
result3 = await get_cashflow_evolution_data(1, "token")
|
|
assert result3 is None
|
|
|
|
result4 = await get_client_invoices(1, "Client", "token")
|
|
assert result4 == [] # Empty list, not None
|
|
|
|
result5 = await get_supplier_invoices(1, "Supplier", "token")
|
|
assert result5 == [] # Empty list, not None
|