82 lines
3.9 KiB
JavaScript
82 lines
3.9 KiB
JavaScript
import { test, after } from 'node:test'
|
|
import assert from 'node:assert/strict'
|
|
import { readFileSync } from 'node:fs'
|
|
import { useTestEnv } from './support/testEnv.js'
|
|
|
|
// Task H2: oeffentliche Shop-Seiten /shop/:siteKey und /shop/:siteKey/widerruf
|
|
useTestEnv({ appwriteUrl: 'http://127.0.0.1:1', stripeUrl: 'http://127.0.0.1:1' })
|
|
const { startTestApp } = await import('./support/testApp.js')
|
|
const { shopSeiteRouter, resetShopSeiteForTests } = await import('../server/routes/shopSeite.js')
|
|
|
|
resetShopSeiteForTests()
|
|
const app = await startTestApp({ '/': shopSeiteRouter() })
|
|
after(() => app.close())
|
|
|
|
const KEY = 'abcdefghijklmnop'.replace(/[ilo]/g, 'a') // nur a-z und 2-7 wie siteKeys
|
|
const get = async (path) => {
|
|
const r = await fetch(app.url + path)
|
|
return { status: r.status, headers: r.headers, text: await r.text() }
|
|
}
|
|
const lies = (pfad) => readFileSync(new URL(`../${pfad}`, import.meta.url), 'utf8')
|
|
|
|
test('gueltiger Schluessel: 200 HTML mit Schluessel im Container und den Schutz-Headern', async () => {
|
|
const r = await get(`/shop/${KEY}`)
|
|
assert.equal(r.status, 200)
|
|
assert.match(r.headers.get('content-type'), /^text\/html; charset=utf-8/)
|
|
assert.match(r.text, new RegExp(`<div data-webklar-shop="${KEY}" data-ansicht="" data-api="">`))
|
|
assert.doesNotMatch(r.text, /__SITEKEY__|__ANSICHT__/)
|
|
assert.match(r.text, /<script src="\/embed\/shop\.js" defer><\/script>/)
|
|
assert.equal(r.headers.get('cache-control'), 'no-store')
|
|
assert.equal(r.headers.get('x-content-type-options'), 'nosniff')
|
|
assert.equal(r.headers.get('referrer-policy'), 'strict-origin-when-cross-origin')
|
|
assert.equal(r.headers.get('x-robots-tag'), 'noindex')
|
|
const csp = r.headers.get('content-security-policy')
|
|
for (const d of ["default-src 'self'", "script-src 'self'", "connect-src 'self'", "img-src 'self' data:", "frame-ancestors 'none'", "base-uri 'none'", "form-action 'self'"]) {
|
|
assert.ok(csp.includes(d), d)
|
|
}
|
|
assert.ok(!/script-src[^;]*unsafe/.test(csp), 'keine unsicheren Skripte')
|
|
})
|
|
|
|
test('/widerruf setzt data-ansicht="widerruf"', async () => {
|
|
const r = await get(`/shop/${KEY}/widerruf`)
|
|
assert.equal(r.status, 200)
|
|
assert.match(r.text, new RegExp(`data-webklar-shop="${KEY}" data-ansicht="widerruf"`))
|
|
assert.equal(r.headers.get('x-robots-tag'), 'noindex')
|
|
})
|
|
|
|
test('jeder syntaktisch gueltige Schluessel liefert dieselbe Seite (kein Orakel)', async () => {
|
|
const a = await get(`/shop/${KEY}`)
|
|
const b = await get('/shop/2222222222222222')
|
|
assert.equal(b.status, 200)
|
|
assert.equal(a.text.replaceAll(KEY, 'X'), b.text.replaceAll('2222222222222222', 'X'))
|
|
})
|
|
|
|
test('ungueltige Schluessel und fremde Pfade: 404', async () => {
|
|
for (const p of ['/shop/xyz', `/shop/${KEY.toUpperCase()}`, `/shop/${KEY}a`, '/shop/abcdefghijklmn01', '/shop/xyz/abc', `/shop/${KEY}/agb`, `/shop/${KEY}/widerruf/x`, '/shop/%3Cscript%3E', '/shop', '/shop/']) {
|
|
const r = await get(p)
|
|
assert.equal(r.status, 404, p)
|
|
assert.doesNotMatch(r.text, /data-webklar-shop/, p)
|
|
}
|
|
})
|
|
|
|
test('nur GET', async () => {
|
|
const r = await fetch(`${app.url}/shop/${KEY}`, { method: 'POST' })
|
|
assert.equal(r.status, 404)
|
|
})
|
|
|
|
test('shop-seite.html: kein Inline-Skript, nur das Widget; in server/index.js vor express.static gemountet', () => {
|
|
const html = lies('public/shop-seite.html')
|
|
const skripte = [...html.matchAll(/<script\b([^>]*)>([\s\S]*?)<\/script>/gi)]
|
|
assert.equal(skripte.length, 1)
|
|
assert.match(skripte[0][1], /\bsrc="\/embed\/shop\.js"/)
|
|
assert.equal(skripte[0][2].trim(), '')
|
|
assert.doesNotMatch(html, /\son[a-z]+\s*=/i, 'keine Inline-Handler')
|
|
assert.doesNotMatch(html, /javascript:/i)
|
|
assert.match(html, /<html lang="de">/)
|
|
assert.match(html, /<title>Shop<\/title>/)
|
|
assert.match(html, /Der Shop wird geladen …/)
|
|
const index = lies('server/index.js')
|
|
const mount = index.indexOf('app.use(shopSeiteRouter())')
|
|
assert.ok(mount > 0 && mount < index.indexOf('app.use(express.static(publicDir))'))
|
|
})
|