"""Test the config editor API endpoints.""" import multiprocessing import time import json import sys import os os.chdir(os.path.dirname(os.path.abspath(__file__))) # Start server import uvicorn import requests def run_server(): uvicorn.run('server:app', host='127.0.0.1', port=8891, log_level='warning') p = multiprocessing.Process(target=run_server, daemon=True) p.start() time.sleep(2) print('=== Server started ===') BASE = 'http://127.0.0.1:8891' AUTH = ('shawn', 'Brett85!@') def t(name, ok, detail=''): status = 'PASS' if ok else 'FAIL' print(f' [{status}] {name}' + (f' — {detail}' if detail else '')) # 1. Health r = requests.get(f'{BASE}/health') t('Health endpoint', r.ok and r.json()['status'] == 'ok') # 2. Config list r = requests.get(f'{BASE}/api/config/list', auth=AUTH) t('Config list returns 200', r.status_code == 200) data = r.json() t('Has files array', 'files' in data and len(data['files']) > 0, f'{len(data["files"])} files') # 3. Read first file first_file = data['files'][0]['path'] r = requests.get(f'{BASE}/api/config/read?path={requests.utils.quote(first_file)}', auth=AUTH) t('Read file returns 200', r.status_code == 200) rd = r.json() t('Has content and metadata', all(k in rd for k in ('content', 'line_count', 'size', 'last_modified'))) t('Line count matches content', rd['line_count'] == len(rd['content'].split('\n'))) # 4. Unauthorized path r = requests.get(f'{BASE}/api/config/read?path=/etc/passwd', auth=AUTH) t('Rejects /etc/passwd', r.status_code == 403) r = requests.get(f'{BASE}/api/config/read?path={requests.utils.quote("/home/oplabs/.bashrc")}', auth=AUTH) t('Rejects .bashrc', r.status_code == 403) # 5. Missing file in whitelist missing_files = [f for f in data['files'] if f.get('missing')] t('Missing files reported correctly', not missing_files or all(f.get('missing') for f in missing_files)) # 6. YAML validation - invalid YAML yaml_files = [f for f in data['files'] if f['filename'].endswith('.yaml')] assert len(yaml_files) > 0, 'Need at least one yaml file to test validation' yaml_path = yaml_files[0]['path'] r = requests.post(f'{BASE}/api/config/save', json={'path': yaml_path, 'content': 'invalid: yaml: [[[bad'}, auth=AUTH) t('Rejects invalid YAML', r.status_code == 400 and 'YAML validation' in r.json().get('error', '')) # 7. Save valid YAML r = requests.post(f'{BASE}/api/config/save', json={'path': yaml_path, 'content': 'key: value\nnested:\n sub: data\n'}, auth=AUTH) t('Saves valid YAML', r.status_code == 200 and r.json().get('saved') and 'backup_path' in r.json()) saved = r.json() print(f' Backup created: {saved["backup_path"]}') # 8. Verify backup exists bak_path = saved['backup_path'] r = requests.get(f'{BASE}/api/config/read?path={requests.utils.quote(first_file)}', auth=AUTH) t('Backup file path is returned', 'bak.' in bak_path) print(f' Original: {saved["path"]}') print(f' Lines: {saved["line_count"]}, Size: {saved["size"]}') # 9. Test backup endpoint directly r = requests.post(f'{BASE}/api/config/backup?path={requests.utils.quote(yaml_path)}', auth=AUTH) t('Manual backup endpoint', r.status_code == 200 and 'backup_path' in r.json()) # 10. HTML serving r = requests.get(f'{BASE}/', auth=AUTH) t('Serves index.html', r.status_code == 200 and 'Config Editor' in r.text) p.terminate() p.join() print('\n=== All tests complete ===')