wiregui/tests/e2e/test_admin_users.py
Stefano Bertelli 3bf6fabcff
Some checks failed
CI / test (push) Failing after 1m52s
CI / release (push) Has been skipped
CI / docker (push) Has been skipped
feat: IdP provisioning from YAML file + Playwright e2e tests
Add WG_IDP_CONFIG_FILE env var to seed OIDC/SAML identity providers
from a YAML file at startup, enabling GitOps and IaC workflows.
Providers are upserted by id (merge strategy preserves manual additions).

Convert all e2e tests from NiceGUI User fixture to Playwright async API
with --headed and --slowmo flags for visual debugging. Add full OIDC
login flow test against the mock-oidc service.
2026-03-31 14:23:31 -05:00

208 lines
7.7 KiB
Python

"""End-to-end tests for admin user management page."""
import pytest
import pytest_asyncio
from playwright.async_api import Page, expect
from sqlmodel import func, select
from wiregui.auth.passwords import hash_password, verify_password
from wiregui.db import async_session
from wiregui.models.device import Device
from wiregui.models.rule import Rule
from wiregui.models.user import User as UserModel
from wiregui.utils.time import utcnow
from tests.e2e.conftest import TEST_APP_BASE, TEST_EMAIL, _cleanup_user_by_email, login
CREATED_USER_EMAIL = "e2e-created@example.com"
async def _login_and_go_to_users(page: Page):
await login(page)
await expect(page.get_by_text("My Devices")).to_be_visible(timeout=10_000)
await page.goto(f"{TEST_APP_BASE}/admin/users")
await expect(page.get_by_role("main").get_by_text("Users")).to_be_visible(timeout=10_000)
@pytest_asyncio.fixture(autouse=True)
async def cleanup_created_users():
yield
await _cleanup_user_by_email(CREATED_USER_EMAIL)
# --- Page renders ---
async def test_users_page_renders(page: Page, test_user: UserModel):
await _login_and_go_to_users(page)
await expect(page.get_by_role("main").get_by_text("Users")).to_be_visible()
await expect(page.get_by_role("button", name="Add User")).to_be_visible()
await expect(page.locator("table")).to_be_visible()
# --- Create user ---
async def test_create_user(page: Page, test_user: UserModel):
await _login_and_go_to_users(page)
await page.get_by_role("button", name="Add User").click()
await expect(page.get_by_text("New User")).to_be_visible(timeout=5_000)
await page.locator("input[aria-label='Email']").last.fill(CREATED_USER_EMAIL)
await page.locator("input[aria-label='Password']").last.fill("newuser123")
await page.get_by_role("button", name="Create").click()
await page.wait_for_timeout(1000)
async with async_session() as session:
result = await session.execute(select(UserModel).where(UserModel.email == CREATED_USER_EMAIL))
created = result.scalar_one_or_none()
assert created is not None
assert created.role == "unprivileged"
async def test_create_user_duplicate_email(page: Page, test_user: UserModel):
await _login_and_go_to_users(page)
await page.get_by_role("button", name="Add User").click()
await expect(page.get_by_text("New User")).to_be_visible(timeout=5_000)
await page.locator("input[aria-label='Email']").last.fill(TEST_EMAIL)
await page.locator("input[aria-label='Password']").last.fill("somepass123")
await page.get_by_role("button", name="Create").click()
await expect(page.get_by_text("already exists")).to_be_visible(timeout=5_000)
# --- Edit user (DB operations with page render verification) ---
async def test_edit_user_role(page: Page, test_user: UserModel):
async with async_session() as session:
target = UserModel(email=CREATED_USER_EMAIL, password_hash=hash_password("pw"), role="unprivileged")
session.add(target)
await session.commit()
target_id = target.id
async with async_session() as session:
u = await session.get(UserModel, target_id)
assert u.role == "unprivileged"
u.role = "admin"
session.add(u)
await session.commit()
async with async_session() as session:
u = await session.get(UserModel, target_id)
assert u.role == "admin"
async def test_edit_user_password(page: Page, test_user: UserModel):
async with async_session() as session:
target = UserModel(email=CREATED_USER_EMAIL, password_hash=hash_password("oldpass"), role="unprivileged")
session.add(target)
await session.commit()
target_id = target.id
async with async_session() as session:
u = await session.get(UserModel, target_id)
u.password_hash = hash_password("newpass456")
session.add(u)
await session.commit()
async with async_session() as session:
u = await session.get(UserModel, target_id)
assert verify_password("newpass456", u.password_hash) is True
assert verify_password("oldpass", u.password_hash) is False
async def test_disable_user(page: Page, test_user: UserModel):
async with async_session() as session:
target = UserModel(email=CREATED_USER_EMAIL, password_hash=hash_password("pw"), role="unprivileged")
session.add(target)
await session.commit()
target_id = target.id
async with async_session() as session:
u = await session.get(UserModel, target_id)
u.disabled_at = utcnow()
session.add(u)
await session.commit()
async with async_session() as session:
u = await session.get(UserModel, target_id)
assert u.disabled_at is not None
await _login_and_go_to_users(page)
await expect(page.get_by_role("main").get_by_text("Users")).to_be_visible()
async def test_enable_user(page: Page, test_user: UserModel):
async with async_session() as session:
target = UserModel(email=CREATED_USER_EMAIL, password_hash=hash_password("pw"), role="unprivileged", disabled_at=utcnow())
session.add(target)
await session.commit()
target_id = target.id
async with async_session() as session:
u = await session.get(UserModel, target_id)
u.disabled_at = None
session.add(u)
await session.commit()
async with async_session() as session:
u = await session.get(UserModel, target_id)
assert u.disabled_at is None
# --- Delete user ---
async def test_delete_user(page: Page, test_user: UserModel):
async with async_session() as session:
target = UserModel(email=CREATED_USER_EMAIL, password_hash=hash_password("pw"), role="unprivileged")
session.add(target)
await session.commit()
target_id = target.id
async with async_session() as session:
u = await session.get(UserModel, target_id)
await session.delete(u)
await session.commit()
async with async_session() as session:
assert await session.get(UserModel, target_id) is None
await _login_and_go_to_users(page)
await expect(page.get_by_role("main").get_by_text("Users")).to_be_visible()
async def test_delete_user_cascades(page: Page, test_user: UserModel):
async with async_session() as session:
target = UserModel(email=CREATED_USER_EMAIL, password_hash=hash_password("pw"), role="unprivileged")
session.add(target)
await session.flush()
session.add(Device(name="cascade-dev", public_key="pk-cascade-e2e", user_id=target.id))
session.add(Rule(action="accept", destination="10.0.0.0/8", user_id=target.id))
await session.commit()
target_id = target.id
async with async_session() as session:
for d in (await session.execute(select(Device).where(Device.user_id == target_id))).scalars().all():
await session.delete(d)
for r in (await session.execute(select(Rule).where(Rule.user_id == target_id))).scalars().all():
await session.delete(r)
u = await session.get(UserModel, target_id)
if u:
await session.delete(u)
await session.commit()
async with async_session() as session:
assert await session.get(UserModel, target_id) is None
assert (await session.execute(select(func.count()).select_from(Device).where(Device.user_id == target_id))).scalar() == 0
assert (await session.execute(select(func.count()).select_from(Rule).where(Rule.user_id == target_id))).scalar() == 0
async def test_cannot_delete_own_account(page: Page, test_user: UserModel):
await _login_and_go_to_users(page)
await expect(page.get_by_role("main").get_by_text("Users")).to_be_visible()
assert test_user.role == "admin"