Files
ocdp-go/test/multitenant_rbac_ui_playwright.py
Ivan087 7f238a3168 refactor: full-stack restructure with multi-tenancy, workspace management, and K8s diagnostics
- Add Workspace domain (entity, repository, service, handler, DTO)
- Add multi-tenant K8s client with tenant binding and quota management
- Add K8s diagnostics client (instance diagnostics)
- Add authorization middleware (authz package)
- Restructure frontend to feature-based architecture (features/)
- Add User Management page in configuration
- Add AccessDenied page and route guards
- Refactor shared components (form inputs, layout, UI)
- Update Tailwind config for new design system
- Add comprehensive documentation (docs/, tasks/, plans)
- Improve cluster service with better kubeconfig handling
- Add tests for crypto, config, helm client, tenant binding
2026-05-12 16:15:14 +08:00

118 lines
4.5 KiB
Python

#!/usr/bin/env python3
# Covers frontend role UI behavior for the multi-tenant/RBAC plan: admin/user
# login, admin-only navigation affordances, user inability to access admin
# resource management routes, and absence of global-shared controls for users.
import os
import sys
try:
from playwright.sync_api import expect, sync_playwright
except ImportError:
print("SKIP: Playwright is not installed; run after installing frontend test dependencies")
sys.exit(77)
FRONTEND_URL = os.environ.get("FRONTEND_URL", "http://localhost:18080")
ADMIN_USER = os.environ.get("ADMIN_USER", os.environ.get("BOOTSTRAP_ADMIN_USER", "admin"))
ADMIN_PASS = os.environ.get("ADMIN_PASS", os.environ.get("BOOTSTRAP_ADMIN_PASS", ""))
USER_A = os.environ.get("USER_A", "")
USER_A_PASS = os.environ.get("USER_A_PASS", "")
def require_env(name: str, value: str) -> None:
if not value:
print(f"SKIP: {name} is required for role UI contract checks")
sys.exit(77)
def login(page, username: str, password: str) -> None:
page.goto(FRONTEND_URL, wait_until="networkidle")
assert "Register" not in page.locator("body").inner_text(timeout=10000), "login page must not expose public registration"
if page.locator("input[type='password']").count() == 0:
page.evaluate("localStorage.clear()")
page.goto(FRONTEND_URL, wait_until="networkidle")
text_inputs = page.locator("input:not([type='password'])")
expect(text_inputs.first).to_be_visible(timeout=10000)
text_inputs.first.fill(username)
page.locator("input[type='password']").first.fill(password)
page.get_by_role("button").filter(has_text="Login").last.click()
page.wait_for_url("**/home", timeout=15000)
page.wait_for_load_state("networkidle")
expect(page.locator("body")).not_to_contain_text("Login failed")
def visible_text(page) -> str:
return page.locator("body").inner_text(timeout=10000)
def assert_user_restrictions(page) -> None:
body = visible_text(page).lower()
forbidden_labels = [
"global shared",
"global_shared",
"make shared",
"all workspaces",
"admin console",
"user management",
"workspace management",
]
found = [label for label in forbidden_labels if label in body]
assert not found, f"user UI exposes admin/global controls: {found}"
admin_paths = [
"/admin",
"/admin/users",
"/admin/workspaces",
"/configuration/users",
"/configuration/workspaces",
]
for path in admin_paths:
page.goto(FRONTEND_URL.rstrip("/") + path, wait_until="networkidle")
page.wait_for_timeout(500)
text = visible_text(page).lower()
assert "forbidden" in text or "unauthorized" in text or page.url.rstrip("/") != FRONTEND_URL.rstrip("/") + path, (
f"user can access admin route {path}"
)
def assert_admin_affordances(page) -> None:
page.goto(FRONTEND_URL.rstrip("/") + "/configuration/clusters", wait_until="networkidle")
page.wait_for_load_state("networkidle")
text = visible_text(page).lower()
expected_any = ["cluster", "registry", "workspace", "user", "admin"]
assert any(item in text for item in expected_any), "admin UI did not render management affordances"
page.goto(FRONTEND_URL.rstrip("/") + "/configuration/users", wait_until="networkidle")
page.wait_for_load_state("networkidle")
text = visible_text(page).lower()
assert "create user" in text and "accounts" in text, "admin user management UI did not render"
if USER_A:
row = page.locator("tr").filter(has_text=USER_A).first
expect(row).to_be_visible(timeout=10000)
delete_button = row.get_by_role("button", name="Delete")
expect(delete_button).to_be_visible(timeout=5000)
box = delete_button.bounding_box()
viewport = page.viewport_size or {"width": 1440, "height": 950}
assert box and 0 <= box["x"] <= viewport["width"] - box["width"], "Delete User button is outside the visible viewport"
require_env("ADMIN_PASS or BOOTSTRAP_ADMIN_PASS", ADMIN_PASS)
require_env("USER_A", USER_A)
require_env("USER_A_PASS", USER_A_PASS)
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
admin_page = browser.new_page(viewport={"width": 1440, "height": 950})
login(admin_page, ADMIN_USER, ADMIN_PASS)
assert_admin_affordances(admin_page)
user_page = browser.new_page(viewport={"width": 1440, "height": 950})
login(user_page, USER_A, USER_A_PASS)
assert_user_restrictions(user_page)
browser.close()
print("PASS: multi-tenant/RBAC UI contract")