dorfteich/apps/web/e2e/admin-users.spec.ts
Claude Fable 5 9cf7b85b93
Some checks failed
CI / Lint, typecheck, test (pull_request) Successful in 6m47s
CI / Build container images (pull_request) Successful in 3m59s
CI / Auth e2e pack (pull_request) Successful in 9m7s
CI / Import/export fidelity gate (pull_request) Successful in 57s
CD / Deploy to Test (push) Blocked by required conditions
CD / Smoke tests against Test (push) Blocked by required conditions
CD / Promote to Int (push) Blocked by required conditions
CI / Build container images (push) Blocked by required conditions
CI / Lint, typecheck, test (push) Has been cancelled
CI / Auth e2e pack (push) Blocked by required conditions
CI / Import/export fidelity gate (push) Blocked by required conditions
CD / Build and push images (push) Has been cancelled
Admin can create user accounts directly (#331)
POST /admin/users (Site-Admin guard) creates an account with the same
field rules as self-registration, but active immediately: the admin
vouches for the address, so the e-mail is marked verified and the
personal pond is provisioned exactly like the verify-email path does
(markEmailVerified alone would skip the pond).

The user manager gains a create dialog (useModalFocus/useDismissable,
Field wiring, flat RHF field names per the #322 lesson). New audit
action user.created_by_admin, catalogue bumped to 1.9.

Tests: api e2e-db (create + immediate login + personal pond, duplicate
username 409, non-admin 403), web e2e through the dialog, and the
admin a11y scan now opens the dialog too. Both packs verified locally
against a fresh stack.

Closes #331
2026-08-05 12:23:01 +02:00

87 lines
3.4 KiB
TypeScript

import { expect, request, test } from '@playwright/test';
import { contextForUser, FIXTURE_PASSWORD } from './helpers';
const BASE_URL = process.env.E2E_BASE_URL ?? 'http://localhost:5173';
/**
* Site-Admin user management UI (issue #59): disabling a user through the admin
* list logs them out and blocks login with a distinct message; enabling
* restores access. (Delete + pseudonymization is covered thoroughly by the api
* db test; the browser pack stays non-destructive so fixtures survive.)
*/
test('disabling a user in the admin UI blocks their login, enabling restores it', async ({
browser,
}) => {
const admin = await contextForUser(browser, BASE_URL, 'fixture-admin');
const login = async (): Promise<number> => {
const ctx = await request.newContext({ baseURL: BASE_URL });
const res = await ctx.post('/api/v1/auth/login', {
data: { usernameOrEmail: 'fixture-viewer', password: FIXTURE_PASSWORD },
});
const status = res.status();
await ctx.dispose();
return status;
};
expect(await login()).toBe(200); // active to begin with
const page = await admin.newPage();
await page.goto('/admin');
await page.locator('.user-manager__search').fill('fixture-viewer');
const row = page.locator('.user-row[data-username="fixture-viewer"]');
await expect(row).toBeVisible();
try {
await row.locator('.user-row__disable').click();
await expect(row.locator('.user-row__status')).toHaveText(/disabled|deaktiviert/i);
expect(await login()).toBe(403); // account_disabled
} finally {
// Re-enable so the fixture is left intact for other packs / reruns.
await row.locator('.user-row__disable').click();
await expect(row.locator('.user-row__status')).toHaveText(/active|aktiv/i);
await admin.close();
}
});
/**
* Direct account creation (issue #331): the dialog creates an active account
* — the new user logs in immediately, no verification hop. The account stays
* in the e2e database; the unique name keeps reruns independent.
*/
test('creating a user in the admin UI yields an account that can log in at once', async ({
browser,
}) => {
const admin = await contextForUser(browser, BASE_URL, 'fixture-admin');
const username = `created-${Date.now()}`;
const password = 'ein sicheres anfangspasswort';
const page = await admin.newPage();
await page.goto('/admin');
await page.locator('.user-manager__create').click();
const dialog = page.getByRole('dialog');
await dialog.getByLabel(/username|benutzername/i).fill(username);
await dialog.getByLabel(/e-mail/i).fill(`${username}@example.org`);
await dialog.getByLabel(/display name|anzeigename/i).fill('Created via UI');
await dialog.getByLabel(/initial password|anfangspasswort/i).fill(password);
await dialog.getByRole('button', { name: /^create$|^anlegen$/i }).click();
await expect(dialog).toBeHidden();
// The list refetches; the fresh account is findable.
await page.locator('.user-manager__search').fill(username);
const row = page.locator(`.user-row[data-username="${username}"]`);
await expect(row).toBeVisible();
await expect(row.locator('.user-row__status')).toHaveText(/active|aktiv/i);
await admin.close();
// No verification mail hop: login works right away.
const ctx = await request.newContext({ baseURL: BASE_URL });
const res = await ctx.post('/api/v1/auth/login', {
data: { usernameOrEmail: username, password },
});
expect(res.status()).toBe(200);
await ctx.dispose();
});