""" Tests for menu builder functions in app/bot/menus.py """ import pytest from app.bot.menus import ( create_main_menu, create_action_buttons, create_client_list_keyboard, create_supplier_list_keyboard, create_invoice_list_keyboard, create_navigation_buttons ) def test_create_main_menu_without_company(): """Test main menu when no company is selected""" keyboard = create_main_menu() assert keyboard is not None assert len(keyboard.inline_keyboard) >= 5 # At least 5 rows # Verify first row is for company selection first_row_text = keyboard.inline_keyboard[0][0].text assert "compan" in first_row_text.lower() or "selectare" in first_row_text.lower() def test_create_main_menu_with_company(): """Test main menu with active company""" keyboard = create_main_menu(company_name="ACME SRL") assert keyboard is not None # Verify that it shows active company first_row = keyboard.inline_keyboard[0][0].text assert "ACME SRL" in first_row or "Selectare" in first_row or "Companie" in first_row def test_main_menu_has_6_financial_buttons(): """Test that menu has 6 financial option buttons""" keyboard = create_main_menu("Test Co") buttons_text = [] # Collect all button texts except first (company) and last (help) rows for row in keyboard.inline_keyboard[1:-1]: for button in row: buttons_text.append(button.text) # Verify we have the expected buttons (partial match for flexibility) expected = ["sold", "casa", "banca", "clien", "furniz", "evol"] found = [any(exp in btn.lower() for btn in buttons_text) for exp in expected] assert all(found), f"Missing buttons. Found: {buttons_text}" def test_main_menu_callback_data_format(): """Test that callback data is correctly formatted""" keyboard = create_main_menu("Test Co") for row in keyboard.inline_keyboard: for button in row: if button.callback_data and button.callback_data.startswith("menu:"): # Verify format: menu:action parts = button.callback_data.split(":") assert len(parts) == 2 assert parts[0] == "menu" assert parts[1] in ["sold", "casa", "banca", "clienti", "furnizori", "evolutie", "select_company"] def test_main_menu_layout_structure(): """Test that main menu has correct layout structure""" keyboard = create_main_menu("Test Co") # Should have: 1 company row + 3 financial rows (2 cols each) + 1 help row = 5 rows minimum assert len(keyboard.inline_keyboard) >= 5 # First row should have 1 button (company selection) assert len(keyboard.inline_keyboard[0]) == 1 # Financial rows should have 2 buttons each for i in range(1, 4): assert len(keyboard.inline_keyboard[i]) == 2 # Last row should have 1 button (help) assert len(keyboard.inline_keyboard[-1]) == 1 def test_create_action_buttons_with_export(): """Test action buttons with export enabled""" keyboard = create_action_buttons("sold", show_export=True) assert len(keyboard.inline_keyboard) == 2 # 2 rows # First row should have 2 buttons (Refresh + Export) assert len(keyboard.inline_keyboard[0]) == 2 # Verify button texts row1_text = [btn.text for btn in keyboard.inline_keyboard[0]] assert any("Refresh" in txt for txt in row1_text) assert any("Export" in txt for txt in row1_text) # Second row should have 1 button (Menu) assert len(keyboard.inline_keyboard[1]) == 1 assert "Meniu" in keyboard.inline_keyboard[1][0].text def test_create_action_buttons_without_export(): """Test action buttons without export""" keyboard = create_action_buttons("sold", show_export=False) all_text = " ".join([btn.text for row in keyboard.inline_keyboard for btn in row]) assert "Export" not in all_text def test_action_buttons_callback_format(): """Test callback format for action buttons""" keyboard = create_action_buttons("sold") for row in keyboard.inline_keyboard: for button in row: if "refresh" in button.text.lower(): assert button.callback_data.startswith("action:refresh:") assert button.callback_data == "action:refresh:sold" elif "menu" in button.text.lower(): assert button.callback_data == "action:menu" elif "export" in button.text.lower(): assert button.callback_data.startswith("action:export:") assert button.callback_data == "action:export:sold" def test_create_client_list_keyboard(): """Test client list keyboard creation""" clients = [ {"id": 1, "name": "Client A", "balance": 15000}, {"id": 2, "name": "Client B", "balance": 8500} ] keyboard = create_client_list_keyboard(clients) # Should have 2 clients + 1 navigation row = 3 rows assert len(keyboard.inline_keyboard) >= 3 # Verify first two rows are for clients assert "Client A" in keyboard.inline_keyboard[0][0].text assert "15" in keyboard.inline_keyboard[0][0].text # Amount # Verify callback data assert keyboard.inline_keyboard[0][0].callback_data == "details:client:1" # Verify second client assert "Client B" in keyboard.inline_keyboard[1][0].text assert keyboard.inline_keyboard[1][0].callback_data == "details:client:2" # Last row should be navigation (2 buttons) assert len(keyboard.inline_keyboard[-1]) == 2 def test_create_supplier_list_keyboard(): """Test supplier list keyboard creation""" suppliers = [ {"id": 1, "name": "Supplier A", "balance": 5000} ] keyboard = create_supplier_list_keyboard(suppliers) assert "Supplier A" in keyboard.inline_keyboard[0][0].text assert "details:supplier:1" in keyboard.inline_keyboard[0][0].callback_data # Last row should be navigation assert len(keyboard.inline_keyboard[-1]) == 2 def test_client_list_max_items(): """Test client list respects max_items limit""" clients = [{"id": i, "name": f"Client {i}", "balance": 1000} for i in range(20)] keyboard = create_client_list_keyboard(clients, max_items=5) # Should have: 5 clients + 1 overflow indicator + 1 navigation row = 7 rows assert len(keyboard.inline_keyboard) <= 7 # Verify only first 5 clients are displayed client_rows = [row for row in keyboard.inline_keyboard if len(row) == 1 and "Client" in row[0].text] displayed_clients = [row for row in client_rows if not "încă" in row[0].text] # Exclude overflow indicator assert len(displayed_clients) == 5 # Verify overflow indicator exists overflow_row = [row for row in keyboard.inline_keyboard if len(row) == 1 and "încă" in row[0].text] assert len(overflow_row) == 1 assert "15" in overflow_row[0][0].text # Should say "și încă 15" def test_create_invoice_list_keyboard(): """Test invoice list keyboard creation""" invoices = [ {"id": 1, "number": "FV001", "amount": 5000, "status": "unpaid"}, {"id": 2, "number": "FV002", "amount": 3500, "status": "paid"} ] keyboard = create_invoice_list_keyboard(invoices, partner_type="CLIENTI") # Should have 2 invoices + 1 navigation row = 3 rows assert len(keyboard.inline_keyboard) >= 3 # Verify first invoice assert "FV001" in keyboard.inline_keyboard[0][0].text assert "invoice:CLIENTI:1" in keyboard.inline_keyboard[0][0].callback_data # Verify status text indicator (no emojis) assert "[NEPLATIT]" in keyboard.inline_keyboard[0][0].text or "[PLATIT]" in keyboard.inline_keyboard[0][0].text # Last row should be navigation (2 buttons) assert len(keyboard.inline_keyboard[-1]) == 2 def test_invoice_list_callback_data(): """Test invoice list callback data format""" invoices = [ {"id": 123, "number": "FV123", "amount": 1000, "status": "paid"} ] # Test CLIENTI keyboard_clienti = create_invoice_list_keyboard(invoices, partner_type="CLIENTI") assert keyboard_clienti.inline_keyboard[0][0].callback_data == "invoice:CLIENTI:123" # Test FURNIZORI keyboard_furnizori = create_invoice_list_keyboard(invoices, partner_type="FURNIZORI") assert keyboard_furnizori.inline_keyboard[0][0].callback_data == "invoice:FURNIZORI:123" def test_invoice_list_navigation_buttons(): """Test invoice list navigation buttons are correct""" invoices = [{"id": 1, "number": "FV001", "amount": 1000, "status": "paid"}] # For CLIENTI, back button should go to clienti keyboard_clienti = create_invoice_list_keyboard(invoices, partner_type="CLIENTI") back_button = keyboard_clienti.inline_keyboard[-1][0] assert "Înapoi" in back_button.text assert back_button.callback_data == "nav:back:clienti" # For FURNIZORI, back button should go to furnizori keyboard_furnizori = create_invoice_list_keyboard(invoices, partner_type="FURNIZORI") back_button = keyboard_furnizori.inline_keyboard[-1][0] assert back_button.callback_data == "nav:back:furnizori" def test_create_navigation_buttons(): """Test simple navigation buttons""" keyboard = create_navigation_buttons("menu") assert len(keyboard.inline_keyboard) == 1 assert len(keyboard.inline_keyboard[0]) == 1 button = keyboard.inline_keyboard[0][0] assert "Înapoi" in button.text assert button.callback_data == "nav:back:menu" def test_navigation_buttons_different_targets(): """Test navigation buttons with different target locations""" # Test menu target kb_menu = create_navigation_buttons("menu") assert kb_menu.inline_keyboard[0][0].callback_data == "nav:back:menu" # Test clienti target kb_clienti = create_navigation_buttons("clienti") assert kb_clienti.inline_keyboard[0][0].callback_data == "nav:back:clienti" # Test furnizori target kb_furnizori = create_navigation_buttons("furnizori") assert kb_furnizori.inline_keyboard[0][0].callback_data == "nav:back:furnizori" def test_client_list_empty(): """Test client list with empty list""" keyboard = create_client_list_keyboard([]) # Should only have navigation row assert len(keyboard.inline_keyboard) == 1 assert len(keyboard.inline_keyboard[0]) == 2 # Back + Refresh def test_supplier_list_empty(): """Test supplier list with empty list""" keyboard = create_supplier_list_keyboard([]) # Should only have navigation row assert len(keyboard.inline_keyboard) == 1 assert len(keyboard.inline_keyboard[0]) == 2 # Back + Refresh def test_invoice_list_empty(): """Test invoice list with empty list""" keyboard = create_invoice_list_keyboard([], partner_type="CLIENTI") # Should only have navigation row assert len(keyboard.inline_keyboard) == 1 assert len(keyboard.inline_keyboard[0]) == 2 # Back + Export def test_balance_formatting(): """Test that balances are formatted with thousands separator""" clients = [ {"id": 1, "name": "Client", "balance": 150000} ] keyboard = create_client_list_keyboard(clients) button_text = keyboard.inline_keyboard[0][0].text # Should have comma separator: 150,000 assert "150,000" in button_text or "150.000" in button_text def test_callback_data_no_spaces(): """Test that callback_data contains no spaces""" keyboard = create_main_menu("Test Company With Spaces") for row in keyboard.inline_keyboard: for button in row: if button.callback_data: assert " " not in button.callback_data, f"Callback data contains space: {button.callback_data}" def test_noop_callback_for_overflow(): """Test that overflow indicators use noop callback""" clients = [{"id": i, "name": f"Client {i}", "balance": 1000} for i in range(15)] keyboard = create_client_list_keyboard(clients, max_items=5) # Find overflow indicator button overflow_buttons = [ row[0] for row in keyboard.inline_keyboard if len(row) == 1 and "încă" in row[0].text ] assert len(overflow_buttons) == 1 assert overflow_buttons[0].callback_data == "noop"