wiregui/tests/e2e/test_account.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

106 lines
5.1 KiB
Python

"""End-to-end tests for account page — password, API tokens, TOTP, deletion."""
from playwright.async_api import Page, expect
from sqlmodel import select
from wiregui.auth.passwords import hash_password
from wiregui.db import async_session
from wiregui.models.user import User as UserModel
from tests.e2e.conftest import TEST_APP_BASE, TEST_EMAIL, TEST_PASSWORD, login
async def _login_to_account(page: Page):
"""Log in and navigate to account 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}/account")
await expect(page.get_by_text("Account Settings")).to_be_visible(timeout=10_000)
async def test_change_password(page: Page, test_user: UserModel):
await _login_to_account(page)
await page.locator("input[aria-label='Current Password']").fill(TEST_PASSWORD)
await page.locator("input[aria-label='New Password']").fill("newpass12345")
await page.locator("input[aria-label='Confirm Password']").fill("newpass12345")
await page.get_by_role("button", name="Update Password").click()
await expect(page.get_by_text("Password changed")).to_be_visible(timeout=5_000)
async def test_change_password_wrong_current(page: Page, test_user: UserModel):
await _login_to_account(page)
await page.locator("input[aria-label='Current Password']").fill("wrongpassword")
await page.locator("input[aria-label='New Password']").fill("newpass12345")
await page.locator("input[aria-label='Confirm Password']").fill("newpass12345")
await page.get_by_role("button", name="Update Password").click()
await expect(page.get_by_text("Wrong current password")).to_be_visible(timeout=5_000)
async def test_change_password_mismatch(page: Page, test_user: UserModel):
await _login_to_account(page)
await page.locator("input[aria-label='Current Password']").fill(TEST_PASSWORD)
await page.locator("input[aria-label='New Password']").fill("newpass12345")
await page.locator("input[aria-label='Confirm Password']").fill("differentpass")
await page.get_by_role("button", name="Update Password").click()
await expect(page.get_by_text("Passwords don't match")).to_be_visible(timeout=5_000)
async def test_change_password_too_short(page: Page, test_user: UserModel):
await _login_to_account(page)
await page.locator("input[aria-label='Current Password']").fill(TEST_PASSWORD)
await page.locator("input[aria-label='New Password']").fill("short")
await page.locator("input[aria-label='Confirm Password']").fill("short")
await page.get_by_role("button", name="Update Password").click()
await expect(page.get_by_text("Min 8 characters")).to_be_visible(timeout=5_000)
async def test_create_api_token(page: Page, test_user: UserModel):
await _login_to_account(page)
await expect(page.get_by_text("No API tokens.")).to_be_visible()
await page.get_by_role("button", name="Add API Token").click()
await expect(page.get_by_text("Copy now")).to_be_visible(timeout=5_000)
async def test_totp_registration_flow(page: Page, test_user: UserModel):
"""Test starting TOTP registration shows QR and verify form."""
await _login_to_account(page)
await expect(page.get_by_text("No MFA methods configured.")).to_be_visible()
await page.get_by_role("button", name="Add TOTP Method").click()
await expect(page.get_by_text("Register TOTP Authenticator")).to_be_visible(timeout=5_000)
await expect(page.get_by_role("button", name="Verify & Save")).to_be_visible()
async def test_totp_verify_invalid_code(page: Page, test_user: UserModel):
await _login_to_account(page)
await page.get_by_role("button", name="Add TOTP Method").click()
await expect(page.get_by_text("Register TOTP Authenticator")).to_be_visible(timeout=5_000)
await page.locator("input[aria-label='6-digit verification code']").fill("000000")
await page.get_by_role("button", name="Verify & Save").click()
await expect(page.get_by_text("Invalid code")).to_be_visible(timeout=5_000)
async def test_delete_account(page: Page, test_user: UserModel):
"""Test account deletion flow with email confirmation."""
async with async_session() as session:
second_admin = UserModel(
email="admin2@example.com",
password_hash=hash_password("admin2pass"),
role="admin",
)
session.add(second_admin)
await session.commit()
try:
await _login_to_account(page)
await page.get_by_role("button", name="Delete Your Account").click()
await expect(page.get_by_text("Delete Your Account?")).to_be_visible(timeout=5_000)
await page.locator(".q-dialog input").fill(TEST_EMAIL)
await page.get_by_role("button", name="Delete My Account").click()
await expect(page.get_by_role("button", name="Sign in", exact=True)).to_be_visible(timeout=10_000)
finally:
async with async_session() as session:
a2 = (await session.execute(
select(UserModel).where(UserModel.email == "admin2@example.com")
)).scalar_one_or_none()
if a2:
await session.delete(a2)
await session.commit()