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>
366 lines
11 KiB
Python
366 lines
11 KiB
Python
"""
|
|
Unit tests for bot response formatters.
|
|
"""
|
|
|
|
import pytest
|
|
from app.bot.formatters import (
|
|
format_dashboard_response,
|
|
format_invoices_response,
|
|
format_treasury_response
|
|
)
|
|
|
|
|
|
class TestDashboardFormatter:
|
|
"""Tests for dashboard response formatter."""
|
|
|
|
def test_format_dashboard_basic(self):
|
|
"""Test basic dashboard formatting."""
|
|
data = {
|
|
'sold_total': 150000.50,
|
|
'facturi_emise': 45,
|
|
'facturi_platite': 30,
|
|
'facturi_neplatite': 15,
|
|
'total_incasari': 200000.00,
|
|
'total_plati': 80000.00
|
|
}
|
|
company_name = "ACME SRL"
|
|
|
|
result = format_dashboard_response(data, company_name)
|
|
|
|
# Check header
|
|
assert "📊 **Dashboard Financiar**" in result
|
|
|
|
# Check sold total
|
|
assert "💰 **Sold Total:** 150,000.50 RON" in result
|
|
|
|
# Check facturi stats
|
|
assert "📄 **Facturi:**" in result
|
|
assert "Emise: 45" in result
|
|
assert "Plătite: 30" in result
|
|
assert "Neplătite: 15" in result
|
|
|
|
# Check cash flow
|
|
assert "💵 **Cash Flow:**" in result
|
|
assert "Încasări: 200,000.00 RON" in result
|
|
assert "Plăți: 80,000.00 RON" in result
|
|
assert "Net: 120,000.00 RON" in result
|
|
|
|
# Check footer
|
|
assert "ACME SRL" in result
|
|
assert "/selectcompany" in result
|
|
|
|
def test_format_dashboard_zero_values(self):
|
|
"""Test dashboard with zero values."""
|
|
data = {
|
|
'sold_total': 0,
|
|
'facturi_emise': 0,
|
|
'facturi_platite': 0,
|
|
'facturi_neplatite': 0,
|
|
'total_incasari': 0,
|
|
'total_plati': 0
|
|
}
|
|
company_name = "TEST SRL"
|
|
|
|
result = format_dashboard_response(data, company_name)
|
|
|
|
assert "0.00 RON" in result
|
|
assert "Emise: 0" in result
|
|
assert "TEST SRL" in result
|
|
|
|
def test_format_dashboard_large_numbers(self):
|
|
"""Test dashboard with large numbers (millions)."""
|
|
data = {
|
|
'sold_total': 1234567.89,
|
|
'facturi_emise': 999,
|
|
'facturi_platite': 500,
|
|
'facturi_neplatite': 499,
|
|
'total_incasari': 9876543.21,
|
|
'total_plati': 5432100.00
|
|
}
|
|
company_name = "BIG CORP SA"
|
|
|
|
result = format_dashboard_response(data, company_name)
|
|
|
|
# Check proper number formatting with commas
|
|
assert "1,234,567.89 RON" in result
|
|
assert "9,876,543.21 RON" in result
|
|
assert "5,432,100.00 RON" in result
|
|
|
|
def test_format_dashboard_missing_fields(self):
|
|
"""Test dashboard with missing fields (defaults to 0)."""
|
|
data = {} # Empty data
|
|
company_name = "EMPTY SRL"
|
|
|
|
result = format_dashboard_response(data, company_name)
|
|
|
|
# Should use defaults (0)
|
|
assert "0.00 RON" in result
|
|
assert "Emise: 0" in result
|
|
|
|
|
|
class TestInvoicesFormatter:
|
|
"""Tests for invoices response formatter."""
|
|
|
|
def test_format_invoices_basic(self):
|
|
"""Test basic invoice list formatting."""
|
|
invoices = [
|
|
{
|
|
'seria': 'FAC',
|
|
'numar': '001',
|
|
'client': 'Client ABC',
|
|
'suma_totala': 5000.00,
|
|
'status': 'platit'
|
|
},
|
|
{
|
|
'seria': 'FAC',
|
|
'numar': '002',
|
|
'client': 'Client XYZ',
|
|
'suma_totala': 3500.50,
|
|
'status': 'neplatit'
|
|
}
|
|
]
|
|
company_name = "TEST SRL"
|
|
|
|
result = format_invoices_response(invoices, company_name)
|
|
|
|
# Check header with count
|
|
assert "📄 **Facturi** (2 total)" in result
|
|
|
|
# Check first invoice
|
|
assert "✅ **FAC001**" in result
|
|
assert "Client ABC - 5,000.00 RON" in result
|
|
assert "Status: platit" in result
|
|
|
|
# Check second invoice
|
|
assert "⏳ **FAC002**" in result
|
|
assert "Client XYZ - 3,500.50 RON" in result
|
|
assert "Status: neplatit" in result
|
|
|
|
# Check footer
|
|
assert "TEST SRL" in result
|
|
|
|
def test_format_invoices_empty_list(self):
|
|
"""Test with empty invoice list."""
|
|
invoices = []
|
|
company_name = "EMPTY SRL"
|
|
|
|
result = format_invoices_response(invoices, company_name)
|
|
|
|
assert "Nu s-au găsit facturi cu aceste criterii." in result
|
|
|
|
def test_format_invoices_limit(self):
|
|
"""Test invoice list with limit."""
|
|
# Create 15 invoices
|
|
invoices = [
|
|
{
|
|
'seria': 'FAC',
|
|
'numar': f'{i:03d}',
|
|
'client': f'Client {i}',
|
|
'suma_totala': 1000 * i,
|
|
'status': 'platit'
|
|
}
|
|
for i in range(1, 16)
|
|
]
|
|
company_name = "MANY SRL"
|
|
|
|
result = format_invoices_response(invoices, company_name, limit=10)
|
|
|
|
# Should show only 10 invoices
|
|
assert "**FAC001**" in result
|
|
assert "**FAC010**" in result
|
|
assert "**FAC011**" not in result # 11th should not appear
|
|
|
|
# Should show message about remaining
|
|
assert "și încă 5 facturi" in result
|
|
assert "Folosește filtre" in result
|
|
|
|
def test_format_invoices_status_emoji(self):
|
|
"""Test status emoji selection."""
|
|
invoices_platit = [
|
|
{'seria': 'A', 'numar': '1', 'client': 'X', 'suma_totala': 100, 'status': 'platit'}
|
|
]
|
|
invoices_neplatit = [
|
|
{'seria': 'B', 'numar': '2', 'client': 'Y', 'suma_totala': 200, 'status': 'neplatit'}
|
|
]
|
|
|
|
result_platit = format_invoices_response(invoices_platit, "TEST")
|
|
result_neplatit = format_invoices_response(invoices_neplatit, "TEST")
|
|
|
|
assert "✅" in result_platit
|
|
assert "⏳" in result_neplatit
|
|
|
|
def test_format_invoices_missing_fields(self):
|
|
"""Test invoices with missing fields."""
|
|
invoices = [
|
|
{
|
|
'seria': '',
|
|
'numar': '',
|
|
# 'client' missing
|
|
# 'suma_totala' missing
|
|
# 'status' missing
|
|
}
|
|
]
|
|
company_name = "TEST SRL"
|
|
|
|
result = format_invoices_response(invoices, company_name)
|
|
|
|
# Should handle missing fields gracefully
|
|
assert "N/A" in result
|
|
assert "0.00 RON" in result
|
|
|
|
|
|
class TestTreasuryFormatter:
|
|
"""Tests for treasury response formatter."""
|
|
|
|
def test_format_treasury_basic(self):
|
|
"""Test basic treasury formatting."""
|
|
data = {
|
|
'cash_balance': 50000.00,
|
|
'bank_accounts': [
|
|
{'banca': 'BCR', 'sold': 100000.00},
|
|
{'banca': 'BRD', 'sold': 75000.50}
|
|
],
|
|
'incoming_payments': 25000.00,
|
|
'outgoing_payments': 15000.00
|
|
}
|
|
company_name = "TREASURY SRL"
|
|
|
|
result = format_treasury_response(data, company_name)
|
|
|
|
# Check header
|
|
assert "💰 **Trezorerie**" in result
|
|
|
|
# Check cash balance
|
|
assert "💵 **Sold Cash:** 50,000.00 RON" in result
|
|
|
|
# Check bank accounts
|
|
assert "🏦 **Conturi Bancare:** 2" in result
|
|
assert "BCR: 100,000.00 RON" in result
|
|
assert "BRD: 75,000.50 RON" in result
|
|
|
|
# Check payments
|
|
assert "📊 **Plăți Programate:**" in result
|
|
assert "De încasat: 25,000.00 RON" in result
|
|
assert "De plătit: 15,000.00 RON" in result
|
|
|
|
# Check footer
|
|
assert "TREASURY SRL" in result
|
|
|
|
def test_format_treasury_no_bank_accounts(self):
|
|
"""Test treasury without bank accounts."""
|
|
data = {
|
|
'cash_balance': 10000.00,
|
|
'incoming_payments': 5000.00,
|
|
'outgoing_payments': 3000.00
|
|
}
|
|
company_name = "CASH ONLY SRL"
|
|
|
|
result = format_treasury_response(data, company_name)
|
|
|
|
# Bank accounts section should not appear
|
|
assert "🏦 **Conturi Bancare:**" not in result
|
|
|
|
# Other sections should be present
|
|
assert "💵 **Sold Cash:**" in result
|
|
assert "📊 **Plăți Programate:**" in result
|
|
|
|
def test_format_treasury_many_bank_accounts(self):
|
|
"""Test treasury with many bank accounts (max 5 shown)."""
|
|
data = {
|
|
'cash_balance': 0,
|
|
'bank_accounts': [
|
|
{'banca': f'Banca {i}', 'sold': i * 1000}
|
|
for i in range(1, 11) # 10 accounts
|
|
],
|
|
'incoming_payments': 0,
|
|
'outgoing_payments': 0
|
|
}
|
|
company_name = "MANY BANKS SRL"
|
|
|
|
result = format_treasury_response(data, company_name)
|
|
|
|
# Should show 10 in count
|
|
assert "🏦 **Conturi Bancare:** 10" in result
|
|
|
|
# But only first 5 in list
|
|
assert "Banca 1:" in result
|
|
assert "Banca 5:" in result
|
|
assert "Banca 6:" not in result
|
|
|
|
def test_format_treasury_zero_values(self):
|
|
"""Test treasury with zero values."""
|
|
data = {
|
|
'cash_balance': 0,
|
|
'incoming_payments': 0,
|
|
'outgoing_payments': 0
|
|
}
|
|
company_name = "ZERO SRL"
|
|
|
|
result = format_treasury_response(data, company_name)
|
|
|
|
assert "0.00 RON" in result
|
|
|
|
def test_format_treasury_large_numbers(self):
|
|
"""Test treasury with large numbers."""
|
|
data = {
|
|
'cash_balance': 9876543.21,
|
|
'bank_accounts': [
|
|
{'banca': 'BIG BANK', 'sold': 12345678.90}
|
|
],
|
|
'incoming_payments': 5555555.55,
|
|
'outgoing_payments': 3333333.33
|
|
}
|
|
company_name = "BIG MONEY SA"
|
|
|
|
result = format_treasury_response(data, company_name)
|
|
|
|
# Check proper number formatting
|
|
assert "9,876,543.21 RON" in result
|
|
assert "12,345,678.90 RON" in result
|
|
assert "5,555,555.55 RON" in result
|
|
assert "3,333,333.33 RON" in result
|
|
|
|
|
|
class TestFormatterIntegration:
|
|
"""Integration tests for formatters."""
|
|
|
|
def test_all_formatters_have_footer(self):
|
|
"""Ensure all formatters include company context footer."""
|
|
company = "INTEGRATION TEST SRL"
|
|
|
|
# Dashboard
|
|
dash_result = format_dashboard_response({}, company)
|
|
assert company in dash_result
|
|
assert "/selectcompany" in dash_result
|
|
|
|
# Invoices (non-empty)
|
|
inv_result = format_invoices_response([
|
|
{'seria': 'A', 'numar': '1', 'client': 'X', 'suma_totala': 100, 'status': 'platit'}
|
|
], company)
|
|
assert company in inv_result
|
|
assert "/selectcompany" in inv_result
|
|
|
|
# Treasury
|
|
treas_result = format_treasury_response({}, company)
|
|
assert company in treas_result
|
|
assert "/selectcompany" in treas_result
|
|
|
|
def test_number_formatting_consistency(self):
|
|
"""Test that all formatters use consistent number formatting."""
|
|
test_amount = 1234567.89
|
|
|
|
# Dashboard
|
|
dash_data = {'sold_total': test_amount}
|
|
dash_result = format_dashboard_response(dash_data, "TEST")
|
|
assert "1,234,567.89" in dash_result
|
|
|
|
# Invoices
|
|
inv_data = [{'seria': 'A', 'numar': '1', 'client': 'X', 'suma_totala': test_amount, 'status': 'platit'}]
|
|
inv_result = format_invoices_response(inv_data, "TEST")
|
|
assert "1,234,567.89" in inv_result
|
|
|
|
# Treasury
|
|
treas_data = {'cash_balance': test_amount}
|
|
treas_result = format_treasury_response(treas_data, "TEST")
|
|
assert "1,234,567.89" in treas_result
|