feat: allow admin email updates in UI (#36)

This commit is contained in:
Paul Nothaft
2025-10-13 17:21:03 +02:00
parent f20472ca26
commit 3c2a79a31a
9 changed files with 404 additions and 12 deletions
+57
View File
@@ -0,0 +1,57 @@
import { test, expect } from '@playwright/test';
const ADMIN_EMAIL = process.env.ADMIN_EMAIL || 'admin@example.com';
const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD || 'Admin!234';
test('admin can update account email via settings page', async ({ page }, testInfo) => {
if (testInfo.project.name === 'mobile-chrome') {
test.skip('Account settings UI is validated on desktop viewport');
}
const newEmail = `admin+playwright-${Date.now()}@example.com`;
await page.goto('/admin/login');
await page.getByLabel(/Email|E-Mail/i).fill(ADMIN_EMAIL);
await page.getByLabel(/Password|Passwort/i).fill(ADMIN_PASSWORD);
await page.getByRole('button', { name: /Sign In|Log in|Anmelden/i }).click();
await expect(page.getByRole('heading', { name: /Dashboard|Übersicht/i })).toBeVisible({ timeout: 20000 });
await page.goto('/admin/settings');
const emailInput = page.getByLabel(/Admin (Email|E-Mail)/i);
const usernameInput = page.getByLabel(/Admin (Username|Benutzername)/i);
await expect(emailInput).toBeVisible();
const originalUsername = await usernameInput.inputValue();
await emailInput.fill(newEmail);
const saveButton = page.getByRole('button', { name: /(Save account details|Kontodaten speichern)/i });
await saveButton.click();
await expect(emailInput).toHaveValue(newEmail, { timeout: 10000 });
await expect(page.locator('.Toastify__toast').filter({ hasText: /(Account details updated|Kontodaten aktualisiert)/i })).toBeVisible({ timeout: 10000 });
await expect(page.getByText(newEmail, { exact: false })).toBeVisible();
const newLoginResponse = await page.request.post('/api/auth/admin/login', {
data: {
username: newEmail,
password: ADMIN_PASSWORD,
},
});
expect(newLoginResponse.ok()).toBeTruthy();
await emailInput.fill(ADMIN_EMAIL);
await usernameInput.fill(originalUsername);
await saveButton.click();
await expect(emailInput).toHaveValue(ADMIN_EMAIL, { timeout: 10000 });
await expect(page.locator('.Toastify__toast').filter({ hasText: /(Account details updated|Kontodaten aktualisiert)/i })).toBeVisible({ timeout: 10000 });
const revertLoginResponse = await page.request.post('/api/auth/admin/login', {
data: {
username: ADMIN_EMAIL,
password: ADMIN_PASSWORD,
},
});
expect(revertLoginResponse.ok()).toBeTruthy();
});
+12 -2
View File
@@ -86,8 +86,18 @@ test('admin login and gallery viewing smoke test', async ({ page }) => {
// Visit gallery share link and authenticate
await page.goto(shareLink);
const passwordField = page.getByPlaceholder(/gallery password/i);
await passwordField.fill(GALLERY_PASSWORD);
await page.getByRole('button', { name: /View Gallery/i }).click();
if (await passwordField.count()) {
await passwordField.fill(GALLERY_PASSWORD);
}
const viewButton = page.getByRole('button', { name: /View Gallery/i });
if (await viewButton.count()) {
try {
await viewButton.click({ noWaitAfter: true, timeout: 2000 });
} catch {
// Already inside gallery view.
}
}
// Wait for photos grid to appear
const tiles = page.locator('.relative.group');
+27 -4
View File
@@ -1,10 +1,26 @@
import { test, expect } from '@playwright/test';
import fs from 'fs';
import path from 'path';
const ADMIN_EMAIL = process.env.ADMIN_EMAIL || 'admin@example.com';
const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD || 'Admin!234';
const GALLERY_PASSWORD = process.env.GALLERY_PASSWORD || 'ExternalMediaPass!1';
async function createExternalGallery(page) {
const externalRoot = path.join(process.cwd(), 'storage', 'external-media', 'picsum-demo', 'individual');
if (!fs.existsSync(externalRoot)) {
fs.mkdirSync(externalRoot, { recursive: true });
}
const sampleImages = ['img1.png', 'img2.png'];
for (const imageName of sampleImages) {
const source = path.join(process.cwd(), 'test-assets', imageName);
const target = path.join(externalRoot, imageName);
if (!fs.existsSync(target)) {
fs.copyFileSync(source, target);
}
}
const loginResponse = await page.request.post('/api/auth/admin/login', {
data: {
username: ADMIN_EMAIL,
@@ -72,7 +88,10 @@ async function createExternalGallery(page) {
failOnStatusCode: false,
});
expect(importResponse.ok()).toBeTruthy();
if (!importResponse.ok()) {
const bodyText = await importResponse.text();
throw new Error(`Failed to import external media: ${importResponse.status()} ${bodyText}`);
}
const importBody = await importResponse.json();
expect(importBody.imported).toBeGreaterThan(0);
@@ -113,9 +132,13 @@ test.describe('External media gallery behavior', () => {
await page.waitForLoadState('domcontentloaded');
const passwordField = page.getByPlaceholder(/gallery password/i).first();
await expect(passwordField).toBeVisible();
await passwordField.fill(GALLERY_PASSWORD);
await page.getByRole('button', { name: /View Gallery/i }).click();
if (await passwordField.count()) {
await passwordField.fill(GALLERY_PASSWORD);
const viewButton = page.getByRole('button', { name: /View Gallery/i });
if (await viewButton.count()) {
await viewButton.click({ noWaitAfter: true, timeout: 2000 });
}
}
const tiles = page.locator('.relative.group');
await expect(tiles.first()).toBeVisible({ timeout: 20000 });