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