Files
Webklar-Kundenbereich/test/ui/admin-funktionen.ui.mjs
T
2026-09-30 16:56:41 +00:00

96 lines
5.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Playwright-Check admin_funktionen: statischer Server + JSON-Fixtures, nicht Teil von npm test.
// Aufruf: node test/ui/admin-funktionen.ui.mjs
import http from 'node:http'
import { readFile } from 'node:fs/promises'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import { chromium } from '/root/.npm/_npx/db89d7302a373f10/node_modules/playwright-core/index.mjs'
const pub = path.join(path.dirname(fileURLToPath(import.meta.url)), '../../public')
const TYPES = { '.html': 'text/html', '.js': 'text/javascript', '.css': 'text/css', '.svg': 'image/svg+xml', '.png': 'image/png', '.ico': 'image/x-icon' }
const news = (state, extra = {}) => ({ state, siteKey: 'abcdefghijklmnop', ticketWoid: null, adminNote: '', ticketMissing: false, ...extra })
const projects = [
{ projectId: 'p1', projectName: 'Bäckerei', features: { news: news('off') } },
{ projectId: 'p2', projectName: 'Café', features: { news: news('setup', { ticketMissing: true }) } },
{ projectId: 'p3', projectName: 'Kiosk', features: { news: news('active', { ticketWoid: '1234' }) } },
]
const posts = []
const json = (res, status, body) => {
res.writeHead(status, { 'content-type': 'application/json' })
res.end(JSON.stringify(body))
}
const server = http.createServer(async (req, res) => {
const url = new URL(req.url, 'http://x')
if (url.pathname === '/api/auth/me') return json(res, 200, { authenticated: true, role: 'admin', admin: { name: 'Kenso', email: 'k@w.de' } })
if (url.pathname === '/api/portal-admin/customers') {
return json(res, 200, { customers: [{ id: 'c1', code: '1001', name: 'Test GmbH', email: 't@t.de', customerStatus: 'customer', portalAccessEnabled: true, projectCount: 3 }] })
}
if (url.pathname === '/api/portal-admin/funktionen/c1') return json(res, 200, { projects })
if (url.pathname === '/api/portal-admin/funktionen/c1/news' && req.method === 'POST') {
let raw = ''
for await (const chunk of req) raw += chunk
const body = JSON.parse(raw)
posts.push(body)
const p = projects.find((x) => x.projectId === body.projectId)
p.features.news = news(body.action === 'activate' ? 'active' : 'disabled', { adminNote: `2026-09-30T10:00:00.000Z Kenso: ${body.action}${body.note ? ` – ${body.note}` : ''}` })
return json(res, 200, { projects: [p] })
}
if (url.pathname.startsWith('/api/')) return json(res, 200, {})
try {
const file = path.join(pub, url.pathname === '/' ? 'admin.html' : url.pathname)
res.writeHead(200, { 'content-type': TYPES[path.extname(file)] || 'application/octet-stream' })
res.end(await readFile(file))
} catch {
res.writeHead(404)
res.end()
}
})
await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve))
let failed = 0
const check = (name, ok, detail = '') => {
console.log(`${ok ? 'OK ' : 'FAIL'} ${name}${detail ? ` (${detail})` : ''}`)
if (!ok) failed += 1
}
const browser = await chromium.launch()
try {
const page = await browser.newPage()
const dialogs = []
page.on('dialog', (d) => { dialogs.push(d.type()); d.dismiss() })
page.on('pageerror', (e) => check('keine Seitenfehler', false, e.message))
await page.goto(`http://127.0.0.1:${server.address().port}/admin.html`)
await page.click('.admin-tab[data-tab="customers"]')
await page.click('[data-features="c1"]')
await page.waitForSelector('[data-feature-project="p1"]')
const row = (id) => page.locator(`[data-feature-project="${id}"]`)
check('3 Zeilen', (await page.locator('[data-feature-project]').count()) === 3)
check('Ticket fehlt sichtbar', (await row('p2').innerText()).includes('Ticket fehlt'))
// Aktivieren: erst Rückfrage in der Zeile, nichts gesendet
await row('p1').locator('[data-feature-ask="activate"]').click()
check('Rückfrage statt Sofort-Aktion', posts.length === 0 && (await row('p1').innerText()).includes('ohne Zahlung aktivieren?'))
await row('p1').locator('[data-feature-no]').click()
check('Abbrechen stellt Knöpfe wieder her', (await row('p1').locator('[data-feature-ask="activate"]').count()) === 1 && posts.length === 0)
await row('p1').locator('[data-feature-ask="activate"]').click()
await row('p1').locator('[data-feature-note]').fill('Kulanz')
await row('p1').locator('[data-feature-yes]').click()
await page.waitForFunction(() => document.querySelector('[data-feature-project="p1"]')?.innerText.includes('Aktiv'))
check('POST activate mit Notiz', JSON.stringify(posts[0]) === JSON.stringify({ projectId: 'p1', action: 'activate', note: 'Kulanz' }), JSON.stringify(posts[0]))
check('Zeile zeigt Aktiv und Notiz', (await row('p1').innerText()).includes('Kenso: activate – Kulanz'))
// Abschalten
await row('p3').locator('[data-feature-ask="disable"]').click()
await row('p3').locator('[data-feature-yes]').click()
await page.waitForFunction(() => document.querySelector('[data-feature-project="p3"]')?.innerText.includes('Abgeschaltet'))
check('Abschalten aktualisiert Zeile', posts[1]?.action === 'disable')
check('keine Browser-Dialoge', dialogs.length === 0)
await page.screenshot({ path: '/tmp/claude-0/-root/a97f1ff7-2f71-45a7-bb9f-eaeacf7e64f1/scratchpad/admin-funktionen.png' })
} finally {
await browser.close()
server.close()
}
console.log(failed ? `admin_funktionen: ${failed} FEHLER` : 'admin_funktionen: alle Prüfungen bestanden')
process.exit(failed ? 1 : 0)