test: add E2E coverage for peer dashboard/services, DNS records, and WG domain access

- test_peer_dashboard_services.py (63 tests): unit tests for all API fixes
  * peer_dashboard field names (name/transfer_rx/transfer_tx vs old stale names)
  * peer_dashboard service_urls dict with correct domain-keyed URLs
  * peer_services email structure (nested smtp/imap, address not username)
  * peer_services files key (not webdav), caldav URL (calendar.dev not radicale.dev:5232)
  * peer_services wireguard DNS (not 10.0.0.1), config text with DNS line
  * DNS zone records (api/webui → Caddy, VIPs for calendar/files/mail/webdav)
  * Caddyfile generation (all service blocks including webui.dev)
  * Access control (401 anon, 403 admin on peer-only routes, 404 missing peer)
- e2e/api/test_peer_endpoints.py: fix stale field assertions, add structure checks
- e2e/wg/test_wg_domain_access.py: E2E WG tests for DNS resolution via VPN tunnel
  * All *.dev domains resolve to correct IPs via CoreDNS
  * api.dev/webui.dev must resolve to Caddy, not container direct IPs
  * CoreDNS reachability through VPN tunnel
  * Peer config DNS field correctness
- e2e/ui/test_peer_dashboard.py: UI checks for service icon links, CalDAV URL, email

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-26 17:41:21 -04:00
parent 3690c6d955
commit 32272420cb
4 changed files with 956 additions and 9 deletions
+65 -4
View File
@@ -4,8 +4,8 @@ Scenarios 20, 21: Peer role access scoping.
Tests cover:
- Peer is blocked from admin-only routes (config, wireguard, peer list)
- Peer can access /api/peer/dashboard and /api/peer/services
- Dashboard response shape (peer_name, online, rx_bytes, tx_bytes, allowed_ips)
- Services response shape (wireguard, email, caldav, webdav sections)
- Dashboard response shape (name, online, transfer_rx, transfer_tx, service_urls)
- Services response shape (wireguard, email, caldav, files sections)
- Peer can change their own password and use the new credential
- Peer cannot call admin/reset-password
"""
@@ -54,12 +54,24 @@ def test_peer_dashboard_has_expected_fields(peer_client):
r = peer_client.get('/api/peer/dashboard')
assert r.status_code == 200
data = r.json()
missing = [f for f in ('peer_name', 'online', 'rx_bytes', 'tx_bytes', 'allowed_ips') if f not in data]
missing = [f for f in ('name', 'online', 'transfer_rx', 'transfer_tx', 'allowed_ips', 'service_urls') if f not in data]
assert not missing, (
f"Dashboard response missing fields {missing}. Got keys: {list(data.keys())}"
)
def test_peer_dashboard_no_stale_field_names(peer_client):
"""Verify renamed fields are gone — old names cause silent UI blanks."""
r = peer_client.get('/api/peer/dashboard')
assert r.status_code == 200
data = r.json()
stale = [f for f in ('peer_name', 'rx_bytes', 'tx_bytes') if f in data]
assert not stale, (
f"Dashboard response still has stale fields {stale}"
"PeerDashboard.jsx reads name/transfer_rx/transfer_tx"
)
def test_peer_can_access_own_services(peer_client):
r = peer_client.get('/api/peer/services')
assert r.status_code == 200, (
@@ -71,12 +83,61 @@ def test_peer_services_has_expected_sections(peer_client):
r = peer_client.get('/api/peer/services')
assert r.status_code == 200
data = r.json()
missing = [k for k in ('wireguard', 'email', 'caldav', 'webdav') if k not in data]
missing = [k for k in ('wireguard', 'email', 'caldav', 'files') if k not in data]
assert not missing, (
f"Services response missing sections {missing}. Got keys: {list(data.keys())}"
)
def test_peer_services_no_stale_keys(peer_client):
"""Verify renamed keys are gone — old names cause silent UI blanks."""
r = peer_client.get('/api/peer/services')
assert r.status_code == 200
data = r.json()
assert 'webdav' not in data, (
"'webdav' still present at top level — MyServices.jsx reads 'files'"
)
def test_peer_services_email_structure(peer_client):
"""Email section must use nested smtp/imap objects and email.address."""
r = peer_client.get('/api/peer/services')
assert r.status_code == 200
email = r.json().get('email', {})
assert 'address' in email, f"email.address missing; email keys: {list(email)}"
assert 'smtp' in email and isinstance(email['smtp'], dict), \
f"email.smtp must be a dict; got: {email.get('smtp')}"
assert 'imap' in email and isinstance(email['imap'], dict), \
f"email.imap must be a dict; got: {email.get('imap')}"
assert 'host' in email['smtp'], "email.smtp.host missing"
assert 'host' in email['imap'], "email.imap.host missing"
assert 'imap_host' not in email, "'imap_host' still flat — should be email.imap.host"
assert 'smtp_host' not in email, "'smtp_host' still flat — should be email.smtp.host"
def test_peer_services_caldav_url_uses_calendar_domain(peer_client):
"""CalDAV URL must be calendar.dev, not radicale.dev:5232."""
r = peer_client.get('/api/peer/services')
assert r.status_code == 200
url = r.json().get('caldav', {}).get('url', '')
assert 'radicale' not in url, \
f"CalDAV URL must not contain 'radicale' — no radicale.dev DNS record; got: {url}"
assert ':5232' not in url, \
f"CalDAV URL exposes port 5232 — use Caddy-proxied URL; got: {url}"
def test_peer_services_wireguard_dns_not_vpn_gateway(peer_client):
"""WireGuard DNS must be the CoreDNS IP, not the VPN gateway 10.0.0.1."""
r = peer_client.get('/api/peer/services')
assert r.status_code == 200
dns = r.json().get('wireguard', {}).get('dns', '')
assert dns != '10.0.0.1', (
"wireguard.dns is 10.0.0.1 (WireGuard VPN gateway) — "
"DNS queries to 10.0.0.1 fail because the VPN server doesn't run a DNS resolver; "
"must be the CoreDNS container IP"
)
# ---------------------------------------------------------------------------
# Auth management — scoping
# ---------------------------------------------------------------------------