# Testing Patterns **Analysis Date:** 2026-02-04 ## Test Framework **Current State:** - **No automated testing detected** in this codebase - No test files found (no `*.test.py`, `*_test.py`, `*.spec.py` files) - No testing configuration files (no `pytest.ini`, `tox.ini`, `setup.cfg`) - No test dependencies in requirements (no pytest, unittest, mock imports) **Implications:** This is a **scripts-only codebase** - all code consists of CLI helper scripts and one bot automation. Manual testing is the primary validation method. ## Script Testing Approach Since this codebase consists entirely of helper scripts and automation, testing is manual and implicit: **Command-Line Validation:** - Each script has a usage/help message showing all commands - Example from `pve`: ```python if len(sys.argv) < 2: print(__doc__) sys.exit(1) ``` - Example from `telegram`: ```bash case "${1:-}" in send) cmd_send "$2" ;; inbox) cmd_inbox ;; *) usage; exit 1 ;; esac ``` **Entry Point Testing:** Main execution guards are used throughout: ```python if __name__ == "__main__": main() ``` This allows scripts to be imported (theoretically) without side effects, though in practice they are not used as modules. ## API Integration Testing **Pattern: Try-Except Fallback:** Many scripts handle multiple service types by trying different approaches: From `pve` script (lines 55-85): ```python def get_status(vmid): """Get detailed status of a VM/container.""" vmid = int(vmid) # Try as container first try: status = pve.nodes(NODE).lxc(vmid).status.current.get() # ... container-specific logic return except: pass # Try as VM try: status = pve.nodes(NODE).qemu(vmid).status.current.get() # ... VM-specific logic return except: pass print(f"VMID {vmid} not found") ``` This is a pragmatic testing pattern: if one API call fails, try another. Useful for development but fragile without structured error handling. ## Command Dispatch Testing **Pattern: Argument Validation:** All scripts validate argument count before executing commands: From `beszel` script (lines 101-124): ```python if __name__ == "__main__": if len(sys.argv) < 2: usage() cmd = sys.argv[1] try: if cmd == "list": cmd_list() elif cmd == "info" and len(sys.argv) == 3: cmd_info(sys.argv[2]) elif cmd == "add" and len(sys.argv) >= 4: # ... else: usage() except Exception as e: print(f"Error: {e}") sys.exit(1) ``` This catches typos in command names and wrong argument counts, showing usage help. ## Data Processing Testing **Bash String Parsing:** Complex regex patterns used in `pbs` script require careful testing: From `pbs` (lines 122-143): ```bash ssh_pbs 'tail -500 /var/log/proxmox-backup/tasks/archive 2>/dev/null' | while IFS= read -r line; do if [[ "$line" =~ UPID:pbs:[^:]+:[^:]+:[^:]+:([0-9A-Fa-f]+):([^:]+):([^:]+):.*\ [0-9A-Fa-f]+\ (OK|ERROR|WARNINGS[^$]*) ]]; then task_time=$((16#${BASH_REMATCH[1]})) task_type="${BASH_REMATCH[2]}" task_target="${BASH_REMATCH[3]}" status="${BASH_REMATCH[4]}" # ... process matched groups fi done ``` **Manual Testing Approach:** - Run command against live services - Inspect output format visually - Verify JSON parsing with inline Python: ```bash echo "$gc_json" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('disk-bytes',0))" ``` ## Mock Testing Pattern (Telegram Bot) The telegram bot has one pattern that resembles mocking - subprocess mocking via `run_command()`: From `telegram/bot.py` (lines 60-78): ```python def run_command(cmd: list, timeout: int = 30) -> str: """Run a shell command and return output.""" try: result = subprocess.run( cmd, capture_output=True, text=True, timeout=timeout, env={**os.environ, 'PATH': f"/home/mikkel/bin:{os.environ.get('PATH', '')}"} ) output = result.stdout or result.stderr or "No output" # Telegram has 4096 char limit per message if len(output) > 4000: output = output[:4000] + "\n... (truncated)" return output except subprocess.TimeoutExpired: return "Command timed out" except Exception as e: return f"Error: {e}" ``` This function: - Runs external commands with timeout protection - Handles both stdout and stderr - Truncates output for Telegram's message size limits - Returns error messages instead of raising exceptions This enables testing command handlers by mocking which commands are available. ## Timeout Testing The telegram bot handles timeouts explicitly: From `telegram/bot.py`: ```python result = subprocess.run( ["ping", "-c", "3", "-W", "2", host], capture_output=True, text=True, timeout=10 # 10 second timeout ) ``` Different commands have different timeouts: - `ping_host()`: 10 second timeout - `run_command()`: 30 second default (configurable) - `backups()`: 60 second timeout (passed to run_command) This prevents the bot from hanging on slow/unresponsive services. ## Error Message Testing Scripts validate successful API responses: From `dns` script (lines 62-69): ```bash curl -s "$BASE/zones/records/add?..." | python3 -c " import sys, json data = json.load(sys.stdin) if data['status'] == 'ok': print(f\"Added: {data['response']['addedRecord']['name']} -> ...\") else: print(f\"Error: {data.get('errorMessage', 'Unknown error')}\") " ``` This pattern: - Parses JSON response - Checks status field - Returns user-friendly error message on failure ## Credential Testing Scripts assume credentials exist and are properly formatted: From `pve` (lines 17-34): ```python creds_path = Path.home() / ".config" / "pve" / "credentials" creds = {} with open(creds_path) as f: for line in f: if "=" in line: key, value = line.strip().split("=", 1) creds[key] = value pve = ProxmoxAPI( creds["host"], user=creds["user"], token_name=creds["token_name"], token_value=creds["token_value"], verify_ssl=False ) ``` **Missing Error Handling:** - No check that credentials file exists - No check that required keys are present - No validation that API connection succeeds - Will crash with KeyError or FileNotFoundError if file missing **Recommendation for Testing:** Add pre-flight validation: ```python required_keys = ["host", "user", "token_name", "token_value"] missing = [k for k in required_keys if k not in creds] if missing: print(f"Error: Missing credentials: {', '.join(missing)}") sys.exit(1) ``` ## File I/O Testing Telegram bot handles file operations defensively: From `telegram/bot.py` (lines 277-286): ```python # Create images directory images_dir = Path(__file__).parent / 'images' images_dir.mkdir(exist_ok=True) # Get the largest photo (best quality) photo = update.message.photo[-1] file = await context.bot.get_file(photo.file_id) # Download the image filename = f"{file_timestamp}.jpg" filepath = images_dir / filename await file.download_to_drive(filepath) ``` **Patterns:** - `mkdir(exist_ok=True)`: Safely creates directory, doesn't error if exists - Timestamp-based filenames to avoid collisions: `f"{file_timestamp}_{original_name}"` - Pathlib for cross-platform path handling ## What to Test If Writing Tests If converting to automated tests, prioritize: **High Priority:** 1. **Telegram bot command dispatch** (`telegram/bot.py` lines 107-366) - Each command handler should have unit tests - Mock `subprocess.run()` to avoid calling actual commands - Test authorization checks (`is_authorized()`) - Test output truncation for large responses 2. **Credential loading** (all helper scripts) - Test missing credentials file error - Test malformed credentials - Test missing required keys 3. **API response parsing** (`dns`, `pbs`, `beszel`, `kuma`) - Test JSON parsing errors - Test malformed responses - Test status code handling **Medium Priority:** 1. **Bash regex parsing** (`pbs` task/error log parsing) - Test hex timestamp conversion - Test status code extraction - Test task target parsing with special characters 2. **Timeout handling** (all `run_command()` calls) - Test command timeout - Test output truncation - Test error message formatting **Low Priority:** 1. Integration tests with real services (kept in separate test suite) 2. Performance tests for large data sets ## Current Test Coverage **Implicit Testing:** - Manual CLI testing during development - Live service testing (commands run against real PVE, PBS, DNS, etc.) - User/admin interaction testing (Telegram bot testing via /start, /status, etc.) **Gap:** - No regression testing - No automated validation of API response formats - No error case testing - No refactoring safety net --- *Testing analysis: 2026-02-04*