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.
208 lines
7.7 KiB
Python
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"
|