homelab/.planning/codebase/TESTING.md
Mikkel Georgsen a639a53b0b docs: add codebase map and domain research
Codebase: 7 documents (stack, architecture, structure, conventions, testing, integrations, concerns)
Research: 5 documents (stack, features, architecture, pitfalls, summary)
2026-02-04 13:50:03 +00:00

324 lines
8.9 KiB
Markdown

# 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*