fc3cfc9741
- api/app.py: email/calendar/files provisioning now best-effort (non-fatal); fixed email_manager.create_email_user call to include domain argument - tests/integration: added module-level auth sessions to all integration test files; added admin auth to api fixture and _resolve_admin_pass() helper; added TEST_PEER_PASSWORD constant; added password to peer creation calls - tests/test_peer_provisioning.py: renamed rollback test to reflect new best-effort semantics (email failure no longer causes rollback) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
114 lines
3.6 KiB
Python
114 lines
3.6 KiB
Python
"""
|
|
Shared fixtures for live integration tests.
|
|
|
|
Configure with environment variables:
|
|
PIC_HOST API host (default: localhost)
|
|
PIC_API_PORT API port (default: 3000)
|
|
PIC_WEBUI_PORT WebUI port (default: 80)
|
|
PIC_WG_CONTAINER WireGuard container name (default: cell-wireguard)
|
|
PIC_ADMIN_USER Admin username (default: admin)
|
|
PIC_ADMIN_PASS Admin password (default: read from data/api/.admin_initial_password or env)
|
|
"""
|
|
import os
|
|
import json
|
|
import subprocess
|
|
import pytest
|
|
import requests
|
|
|
|
PIC_HOST = os.environ.get('PIC_HOST', 'localhost')
|
|
API_PORT = int(os.environ.get('PIC_API_PORT', '3000'))
|
|
WEBUI_PORT = int(os.environ.get('PIC_WEBUI_PORT', '80'))
|
|
WG_CONTAINER = os.environ.get('PIC_WG_CONTAINER', 'cell-wireguard')
|
|
ADMIN_USER = os.environ.get('PIC_ADMIN_USER', 'admin')
|
|
ADMIN_PASS = os.environ.get('PIC_ADMIN_PASS', '')
|
|
|
|
API_BASE = f"http://{PIC_HOST}:{API_PORT}"
|
|
WEBUI_BASE = f"http://{PIC_HOST}:{WEBUI_PORT}"
|
|
|
|
TEST_PEERS = (
|
|
'integration-test-full',
|
|
'integration-test-restricted',
|
|
'integration-test-none',
|
|
'bad-svc-peer', # guard against validation-test leak
|
|
)
|
|
|
|
TEST_PEER_PASSWORD = 'IntegrationTest123!'
|
|
|
|
|
|
def _resolve_admin_pass() -> str:
|
|
if ADMIN_PASS:
|
|
return ADMIN_PASS
|
|
# Try reading from the initial password file (present on first run before bootstrap)
|
|
candidate = os.path.join(
|
|
os.path.dirname(__file__), '..', '..', 'data', 'api', '.admin_initial_password'
|
|
)
|
|
candidate = os.path.normpath(candidate)
|
|
if os.path.exists(candidate):
|
|
return open(candidate).read().strip()
|
|
raise RuntimeError(
|
|
"Admin password unknown. Set PIC_ADMIN_PASS env var or run make setup first."
|
|
)
|
|
|
|
|
|
@pytest.fixture(scope='session')
|
|
def api():
|
|
"""Authenticated requests.Session logged in as admin."""
|
|
s = requests.Session()
|
|
s.headers['Content-Type'] = 'application/json'
|
|
password = _resolve_admin_pass()
|
|
r = s.post(f"{API_BASE}/api/auth/login", json={'username': ADMIN_USER, 'password': password})
|
|
if r.status_code != 200:
|
|
raise RuntimeError(f"Integration test login failed: {r.status_code} {r.text}")
|
|
return s
|
|
|
|
|
|
@pytest.fixture(scope='session')
|
|
def api_base():
|
|
return API_BASE
|
|
|
|
|
|
@pytest.fixture(scope='session')
|
|
def webui_base():
|
|
return WEBUI_BASE
|
|
|
|
|
|
@pytest.fixture(scope='session', autouse=True)
|
|
def cleanup_test_peers(api):
|
|
"""Delete any leftover test peers before and after the entire session."""
|
|
for name in TEST_PEERS:
|
|
api.delete(f"{API_BASE}/api/peers/{name}")
|
|
yield
|
|
for name in TEST_PEERS:
|
|
api.delete(f"{API_BASE}/api/peers/{name}")
|
|
|
|
|
|
def iptables_forward() -> str:
|
|
"""Return iptables-save output from the WireGuard container."""
|
|
result = subprocess.run(
|
|
['docker', 'exec', WG_CONTAINER, 'iptables-save'],
|
|
capture_output=True, text=True, timeout=10,
|
|
)
|
|
return result.stdout
|
|
|
|
|
|
def peer_rules(peer_ip: str) -> list[str]:
|
|
"""Return FORWARD rule lines for a specific peer IP."""
|
|
comment = f'pic-peer-{peer_ip.replace(".", "-")}'
|
|
return [line for line in iptables_forward().splitlines() if comment in line]
|
|
|
|
|
|
def get_live_service_vips(session: requests.Session = None) -> dict:
|
|
"""
|
|
Read virtual IPs from the config API using an authenticated session.
|
|
Falls back to a new unauthenticated request only if no session provided (legacy).
|
|
"""
|
|
s = session or requests.Session()
|
|
cfg = s.get(f"{API_BASE}/api/config").json()
|
|
sips = cfg.get('service_ips', {})
|
|
return {
|
|
'calendar': sips.get('vip_calendar', ''),
|
|
'files': sips.get('vip_files', ''),
|
|
'mail': sips.get('vip_mail', ''),
|
|
'webdav': sips.get('vip_webdav', ''),
|
|
}
|