#!/usr/bin/env python3 """Presentation checks runnable without a Proxmox host.""" import contextlib import importlib.util import io import os from pathlib import Path import subprocess import unittest from unittest.mock import patch ROOT = Path(__file__).resolve().parents[1] spec = importlib.util.spec_from_file_location('disks', ROOT / 'inc/orphan-disks.py') disks = importlib.util.module_from_spec(spec) spec.loader.exec_module(disks) class UITests(unittest.TestCase): def shell(self, script, width=80): result = subprocess.run(['bash', '-c', 'source "$UI_FILE"\nTAPM_UI_INIT\n' + script], capture_output=True, text=True, env=dict(os.environ, UI_FILE=str(ROOT / 'inc/ui.inc'), COLUMNS=str(width), TERM='xterm', NO_COLOR='1')) self.assertEqual(result.returncode, 0, result.stderr) self.assertEqual(result.stderr, '') self.assertNotIn('\x1b', result.stdout) return result.stdout def test_plain_wrapped_menu_and_task(self): output = self.shell(''' TAPM_UI_RULE TAPM_UI_MENU_META 'Storage, ISOs & Backups' orphan_disks TAPM_UI_MENU_ROW 5 $'Find \\e[36morphaned\\e[39m and unused disks [Read-only]' 1 TAPM_UI_TASK 'Find orphaned and unused disks' ''', width=40) self.assertIn('> 5', output) self.assertIn('Starting:', output) self.assertTrue(all(len(line) <= 40 for line in output.splitlines())) def test_submenus_and_choices_do_not_start_tasks(self): cases = [('Main Menu', 'cluster', 'submenu'), ('Host Configuration & Recovery', 'services', 'submenu'), ('Select ISO Storage', 'local', 'choice'), ('Customize TAPM Host Profile', 'utilities', 'choice'), ('Customize TAPM Host Profile', 'apply', 'task'), ('Storage, ISOs & Backups', 'orphan_disks', 'task'), ('Cluster & High Availability', 'back', 'navigation')] for title, value, expected in cases: with self.subTest(title=title, value=value): output = self.shell(f"TAPM_UI_MENU_META '{title}' '{value}'\nprintf '%s' \"$TAPM_UI_KIND\"") self.assertEqual(output, expected) def test_header_widths(self): source = (ROOT / 'proxmenu-scripts.sh').read_text() header = 'MENU_HEADER() {' + source.split('MENU_HEADER() {', 1)[1].split('\nSELECT_MENU()', 1)[0] for width in (40, 80, 120): with self.subTest(width=width): output = self.shell(''' TAPM_LOAD_HEADER_INFO() { TAPM_HEADER_PVE_VERSION=9.2.20; TAPM_HEADER_CLUSTER=Home-Cluster; } LOAD_UPDATE_STATUS() { :; } hostname() { printf ids-vms2; } UPDATE_STATUS=current VERS=2026.9.22-15 RNIP=10.10.2.12 ''' + header + '\nMENU_HEADER', width=width) self.assertIn('TA-ProxMenu', output) self.assertIn('Home-Cluster', output) self.assertIn('Up to date', output) self.assertTrue(all(len(line) <= min(width, 75) for line in output.splitlines())) def test_report_is_plain_when_redirected(self): rows = [dict(node='shared (via ids-vms1)', storage='iSCSI-Datastore2', volume='iSCSI-Datastore2:vm-139-disk-0.qcow2', size_bytes=25 * 1024 ** 3, size_is_approximate=True, status='Unused disk', detail='Webserver-Node1 (VM 139)')] output = io.StringIO() with patch.dict(os.environ, {'COLUMNS': '52'}), contextlib.redirect_stdout(output): disks.render(rows, ['ids-vms3: offline; storage not scanned'], ['one store']) text = output.getvalue() self.assertNotIn('\x1b', text) self.assertIn('Scan finished — incomplete coverage', text) self.assertIn('Webserver-Node1 (VM 139)', text) self.assertIn('~25.00 GiB', text) self.assertTrue(all(len(line) <= 52 for line in text.splitlines())) def test_no_color_on_terminal_when_requested(self): output = io.StringIO() output.isatty = lambda: True with patch.dict(os.environ, {'TERM': 'xterm', 'NO_COLOR': '1'}): disks.ReportDisplay(output).line('Results', 'bold') self.assertNotIn('\x1b', output.getvalue()) if __name__ == '__main__': unittest.main()