0d32038150
Adds tests/e2e/ with three layers of E2E coverage: - API layer (tests/e2e/api/): unauthenticated access, admin endpoints, peer endpoints, access control enforcement — 24 tests - Playwright UI (tests/e2e/ui/): login flows, admin navigation, peer dashboard/services, role-based ACL, password change — 60+ tests - WireGuard connectivity (tests/e2e/wg/): tunnel up/down, DNS resolution through VPN, service ACL enforcement via iptables, full-tunnel routing Shared helpers: PicAPIClient, WGInterface, playwright_login, cleanup. Makefile targets: test-e2e-api, test-e2e-ui, test-e2e-wg, test-e2e. Adds scripts/reset_admin_password.py for test bootstrap. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
115 lines
3.6 KiB
Python
115 lines
3.6 KiB
Python
"""
|
|
Peer access-control tests (scenarios 14 & 15).
|
|
|
|
PrivateRoute.jsx (confirmed):
|
|
- Unauthenticated users → <Navigate to="/login" />
|
|
- Authenticated user with wrong role → <Navigate to="/" />
|
|
|
|
A peer (role='peer') visiting an admin-only route must be redirected to '/'.
|
|
A peer must NOT see admin sidebar links (Peers, Settings, WireGuard, etc.).
|
|
"""
|
|
import pytest
|
|
|
|
pytestmark = pytest.mark.ui
|
|
|
|
# All routes that require role='admin' (from App.jsx Routes).
|
|
ADMIN_ONLY_ROUTES = [
|
|
'/peers',
|
|
'/network',
|
|
'/wireguard',
|
|
'/email',
|
|
'/calendar',
|
|
'/files',
|
|
'/routing',
|
|
'/vault',
|
|
'/containers',
|
|
'/cell-network',
|
|
'/logs',
|
|
'/settings',
|
|
]
|
|
|
|
# Admin-only sidebar link names (from App.jsx adminNavigation).
|
|
ADMIN_ONLY_NAV_LINKS = [
|
|
'Peers',
|
|
'Network Services',
|
|
'WireGuard',
|
|
'Email',
|
|
'Calendar',
|
|
'Files',
|
|
'Routing',
|
|
'Vault',
|
|
'Containers',
|
|
'Cell Network',
|
|
'Logs',
|
|
'Settings',
|
|
]
|
|
|
|
|
|
# ── Scenario 14: peer redirected from admin routes ───────────────────────────
|
|
|
|
@pytest.mark.parametrize('admin_route', ADMIN_ONLY_ROUTES)
|
|
def test_peer_redirected_from_admin_route(peer_page, webui_base, admin_route):
|
|
"""
|
|
A peer navigating to an admin-only route must NOT land on that route.
|
|
PrivateRoute redirects them to '/' instead.
|
|
"""
|
|
page, _ = peer_page
|
|
page.goto(f"{webui_base}{admin_route}")
|
|
page.wait_for_load_state('networkidle')
|
|
|
|
current_path = page.url.replace(webui_base, '')
|
|
assert current_path.rstrip('/') not in [admin_route.rstrip('/')], (
|
|
f"Peer was allowed to reach admin-only route '{admin_route}'. "
|
|
f"Expected redirect to '/'. Got: {page.url}"
|
|
)
|
|
# Must not have been sent to /login either — peer IS authenticated.
|
|
assert '/login' not in page.url, (
|
|
f"Peer was unexpectedly redirected to /login from '{admin_route}'. "
|
|
"PrivateRoute should redirect role-mismatches to '/', not /login."
|
|
)
|
|
|
|
|
|
# ── Scenario 15: peer sidebar lacks admin links ──────────────────────────────
|
|
|
|
def test_peer_nav_does_not_show_admin_only_links(peer_page, webui_base):
|
|
"""
|
|
The peer sidebar (peerNavigation in App.jsx) only contains Dashboard,
|
|
My Services, and Account. Admin-only links must be absent.
|
|
"""
|
|
page, _ = peer_page
|
|
# Navigate to root so the sidebar is fully rendered.
|
|
page.goto(f"{webui_base}/")
|
|
page.wait_for_load_state('networkidle')
|
|
|
|
for link_name in ADMIN_ONLY_NAV_LINKS:
|
|
assert not page.get_by_role('link', name=link_name).is_visible(), (
|
|
f"Admin-only sidebar link '{link_name}' should NOT be visible to a peer"
|
|
)
|
|
|
|
|
|
def test_peer_nav_shows_allowed_links(peer_page, webui_base):
|
|
"""
|
|
The peer sidebar must contain exactly the three peer navigation items:
|
|
Dashboard, My Services, Account.
|
|
"""
|
|
page, _ = peer_page
|
|
page.goto(f"{webui_base}/")
|
|
page.wait_for_load_state('networkidle')
|
|
|
|
for link_name in ('Dashboard', 'My Services', 'Account'):
|
|
assert page.get_by_role('link', name=link_name).is_visible(), (
|
|
f"Peer sidebar should show link '{link_name}'"
|
|
)
|
|
|
|
|
|
def test_peer_my_services_is_accessible(peer_page, webui_base):
|
|
"""
|
|
/my-services is restricted to role='peer' (requireRole="peer" in App.jsx).
|
|
A logged-in peer must be able to reach it.
|
|
"""
|
|
page, _ = peer_page
|
|
page.goto(f"{webui_base}/my-services")
|
|
page.wait_for_load_state('networkidle')
|
|
assert '/login' not in page.url
|
|
assert '/my-services' in page.url
|