import { test, after } from 'node:test' import assert from 'node:assert/strict' import { readFileSync } from 'node:fs' import { startAppwriteMock } from './support/appwriteMock.js' import { startFakeStripe } from './support/fakeStripe.js' import { useTestEnv } from './support/testEnv.js' // Wechsel Ticketsystem -> Projekt-Admin per Einmal-Link (purpose 'switch'), Rueckweg-Button im Admin const aw = await startAppwriteMock() const st = await startFakeStripe() useTestEnv({ appwriteUrl: aw.url, stripeUrl: st.url }) const { config } = await import('../server/config.js') const { startTestApp, post, rawGet } = await import('./support/testApp.js') const { default: authRoutes } = await import('../server/routes/auth.js') const { default: switchLinkRoutes } = await import('../server/routes/admin/switchLink.js') const { createStaffPreviewToken } = await import('../server/services/staffPreviewTokens.js') const app = await startTestApp({ '/api/auth': authRoutes, '/api/admin/switch-link': switchLinkRoutes }) after(() => Promise.all([app.close(), aw.close(), st.close()])) aw.seed('employees', { $id: 'e_staff2', userId: 'u_staff2', displayName: 'Andrej', email: 'andrej@webklar.com' }) const bearer = (jwt) => ({ headers: { authorization: `Bearer ${jwt}` } }) const switchLink = (jwt) => post('/api/admin/switch-link', {}, jwt ? bearer(jwt) : {}) const tokenOf = (openUrl) => new URL(openUrl).searchParams.get('token') const portalSession = (res) => { const cookie = res.headers.getSetCookie().find((c) => c.startsWith(`${config.cookieName}=`)) if (!cookie) return null const raw = decodeURIComponent(cookie.split(';')[0].slice(config.cookieName.length + 1)) return JSON.parse(raw.slice(2, raw.lastIndexOf('.'))) } test('switch-link: ohne, ungueltiges und halbes JWT -> 401', async () => { assert.equal((await switchLink(null)).status, 401) assert.equal((await switchLink('jwt-falsch')).status, 401) assert.equal((await switchLink('jwt-halb')).status, 401) }) test('switch-link: kein Mitarbeiter -> 403', async () => { assert.equal((await switchLink('jwt-kunde')).status, 403) }) test('switch-link: Mitarbeiter ohne Admin-Label bekommt einen Link', async () => { const res = await switchLink('jwt-staff') assert.equal(res.status, 200) assert.match(res.body.openUrl, /^https:\/\/project\.webklar\.com\/api\/auth\/staff-switch\?token=[0-9a-f]{64}$/) }) test('staff-switch: gueltig -> Admin-Sitzung und /admin.html, zweites Mal -> Login', async () => { const { body } = await switchLink('jwt-staff') const path = `/api/auth/staff-switch?token=${tokenOf(body.openUrl)}` const res = await rawGet(path) assert.equal(res.status, 302) assert.equal(res.headers.get('location'), '/admin.html') const session = portalSession(res) assert.equal(session.role, 'admin') assert.equal(session.isAdmin, false) assert.equal(session.appwriteUserId, 'u_staff2') const again = await rawGet(path) assert.equal(again.status, 302) assert.equal(again.headers.get('location'), '/login.html?error=Link%20abgelaufen%2C%20bitte%20anmelden') assert.equal(portalSession(again), null) }) test('staff-switch: nach 61 Sekunden abgelaufen', async (t) => { const { body } = await switchLink('jwt-staff') const now = Date.now() t.mock.method(Date, 'now', () => now + 61 * 1000) const res = await rawGet(`/api/auth/staff-switch?token=${tokenOf(body.openUrl)}`) assert.match(res.headers.get('location'), /^\/login\.html\?error=/) }) test('Tokens nicht vertauschbar: Switch ueber staff-preview, Preview ueber staff-switch', async () => { const { body } = await switchLink('jwt-staff') assert.equal((await rawGet(`/api/auth/staff-preview?token=${tokenOf(body.openUrl)}`)).status, 410) const previewToken = createStaffPreviewToken({ userId: 'u_admin', email: 'kenso@webklar.com', name: 'Kenso', previewUrl: 'https://x.project.webklar.com/' }) const res = await rawGet(`/api/auth/staff-switch?token=${previewToken}`) assert.match(res.headers.get('location'), /^\/login\.html\?error=/) assert.equal(portalSession(res), null) }) test('admin.html: Rueckweg ins Ticketsystem in der Kopfzeile', () => { const html = readFileSync(new URL('../public/admin.html', import.meta.url), 'utf8') const actions = html.slice(html.indexOf('portal-header-actions'), html.indexOf('id="logout-btn"')) assert.match(actions, /← Ticketsystem<\/a>/) })