Add missing test files and update .gitignore to allow test files
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>
This commit is contained in:
365
reports-app/telegram-bot/tests/test_formatters.py
Normal file
365
reports-app/telegram-bot/tests/test_formatters.py
Normal file
@@ -0,0 +1,365 @@
|
||||
"""
|
||||
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
|
||||
Reference in New Issue
Block a user