Eine Quelle der Wahrheit dafür, was die Oberfläche schon zeigen darf, solange der Dienst nur P1 und P3 kann. Der Wert aus dem Bau gilt nur als Ziffer 1 bis 9, sonst die Standardstufe; keine Überschreibung zur Laufzeit. Dazu tabErlaubt (Keller = ?tab=botleads) und linkErlaubt (Links in den Keller, auch über /workers und /leads). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
113 lines
7.6 KiB
JavaScript
113 lines
7.6 KiB
JavaScript
import test from 'node:test'
|
||
import assert from 'node:assert/strict'
|
||
import fs from 'node:fs'
|
||
import path from 'node:path'
|
||
import { fileURLToPath } from 'node:url'
|
||
import esbuild from 'esbuild'
|
||
import { loadModule } from './load.mjs'
|
||
|
||
// Stufen-Sperre der Hustler-Seite (src/lib/hustlerStufe.js): eine Quelle der Wahrheit, was die Oberfläche schon zeigen darf.
|
||
// Stufe 3 = Dienst P3 live (Stand der Produktion): kein Keller (Bot-Leads, P6), keine Antwort-Knöpfe im Pult (P4). Ab Stufe 4 kommen die Antwort-Knöpfe, ab 6 der Keller.
|
||
// Diese Datei prüft das Modul selbst (Stufe aus dem Bau, Schwellen, Bildschirme, Links). Die Bausteine prüfen ihre Sperre in ihren eigenen Tests, jeweils mit Stufe 3 (gesperrt)
|
||
// und Stufe 6 (offen): rowModel in workersPult.test.mjs, der Client in workersApi.test.mjs, Pult in workersPultUi.test.mjs, Haus in hustlerHochhausWelt.test.mjs, Seite in
|
||
// hustlerSeite.test.mjs, Spur-Links in hustlerDialog.test.mjs, der Prüfstand in workersHarness.test.mjs.
|
||
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..')
|
||
const read = (rel) => fs.readFileSync(path.join(ROOT, rel), 'utf8')
|
||
|
||
const stufe = await loadModule('src/lib/hustlerStufe.js')
|
||
const { HUSTLER_STUFE, STUFE_ANTWORTEN, STUFE_KELLER, stufeAus, antwortenOffen, kellerOffen, tabErlaubt, linkErlaubt } = stufe
|
||
|
||
// ---------- Stufe aus dem Bau ----------
|
||
|
||
test('stufeAus: nur eine ganze Zahl 1..9 gilt (Zahl oder einzelne Ziffer im Text, Leerraum außen egal), alles andere ergibt den Standard', () => {
|
||
for (let n = 1; n <= 9; n += 1) {
|
||
assert.equal(stufeAus(n, 3), n); assert.equal(stufeAus(String(n), 3), n); assert.equal(stufeAus(` ${n}\n`, 3), n)
|
||
}
|
||
const ungueltig = [undefined, null, '', ' ', '0', 0, '10', 10, '12', '06', '6.0', '6.5', 6.5, '-1', -1, '+6', 'sechs', 'abc', '6a', 'a6', '6 7', NaN, Infinity, -Infinity, true, false, {}, [], [6], () => 6, '1', '٦']
|
||
for (const roh of ungueltig) assert.equal(stufeAus(roh, 3), 3, `Wert ${String(roh)} (${typeof roh})`)
|
||
// der Standard ist der übergebene
|
||
assert.equal(stufeAus('x', 5), 5); assert.equal(stufeAus(undefined, 7), 7)
|
||
})
|
||
|
||
test('Standard: ohne Wert aus dem Bau gilt die Stufe aus dem Quelltext, einmal und an einer Stelle; sie liegt in 1..9', () => {
|
||
const quelltext = read('src/lib/hustlerStufe.js')
|
||
const treffer = [...quelltext.matchAll(/export const HUSTLER_STUFE = stufeAus\(buildWert\(\), (\d)\)/g)]
|
||
assert.equal(treffer.length, 1, 'genau eine Zeile legt die Stufe fest')
|
||
assert.equal(HUSTLER_STUFE, Number(treffer[0][1]))
|
||
assert.ok(Number.isInteger(HUSTLER_STUFE) && HUSTLER_STUFE >= 1 && HUSTLER_STUFE <= 9)
|
||
assert.ok(quelltext.includes('beim jeweiligen Livegang hier hochsetzen'))
|
||
assert.equal(STUFE_ANTWORTEN, 4); assert.equal(STUFE_KELLER, 6)
|
||
})
|
||
|
||
// Der Wert aus dem Bau (Vite: import.meta.env.VITE_HUSTLER_STUFE, Prüfstand: define von esbuild) wird beim Bauen eingesetzt. Hier derselbe Weg mit esbuild.
|
||
async function stufeNachBau(define) {
|
||
const out = await esbuild.build({
|
||
stdin: { contents: "export { HUSTLER_STUFE, antwortenOffen, kellerOffen } from './src/lib/hustlerStufe.js'", resolveDir: ROOT, loader: 'js' },
|
||
bundle: true, format: 'esm', platform: 'neutral', write: false, define,
|
||
})
|
||
return import(`data:text/javascript;base64,${Buffer.from(out.outputFiles[0].text).toString('base64')}`)
|
||
}
|
||
|
||
test('Bau: VITE_HUSTLER_STUFE überschreibt die Stufe zur Bauzeit (Prüfstand); ungültige Werte fallen auf den Standard zurück', async () => {
|
||
for (const [roh, erwartet] of [['6', 6], ['4', 4], ['9', 9], ['1', 1], [' 6 ', 6], ['3', 3], ['0', HUSTLER_STUFE], ['10', HUSTLER_STUFE], ['abc', HUSTLER_STUFE], ['', HUSTLER_STUFE], ['6.5', HUSTLER_STUFE], ['-2', HUSTLER_STUFE]]) {
|
||
const m = await stufeNachBau({ 'import.meta.env.VITE_HUSTLER_STUFE': JSON.stringify(roh) })
|
||
assert.equal(m.HUSTLER_STUFE, erwartet, `VITE_HUSTLER_STUFE=${JSON.stringify(roh)}`)
|
||
assert.equal(m.kellerOffen(), erwartet >= 6); assert.equal(m.antwortenOffen(), erwartet >= 4)
|
||
}
|
||
// nicht gesetzt (Vite: undefined) und ohne import.meta.env (Node): der Standard
|
||
assert.equal((await stufeNachBau({ 'import.meta.env.VITE_HUSTLER_STUFE': 'undefined' })).HUSTLER_STUFE, HUSTLER_STUFE)
|
||
assert.equal((await stufeNachBau({})).HUSTLER_STUFE, HUSTLER_STUFE)
|
||
})
|
||
|
||
test('keine Überschreibung zur Laufzeit: kein localStorage, keine Adresse, keine Umgebung außer dem Bau; VITE_HUSTLER_STUFE steht nur in hustlerStufe.js', () => {
|
||
const quelltext = read('src/lib/hustlerStufe.js').split('\n').filter((z) => !z.trimStart().startsWith('//')).join('\n')
|
||
for (const verboten of ['localStorage', 'sessionStorage', 'location', 'URLSearchParams', 'document', 'window', 'process.env', 'cookie']) assert.ok(!quelltext.includes(verboten), verboten)
|
||
// genau ein Zugriff auf die Umgebung
|
||
assert.equal(quelltext.match(/import\.meta\.env/g).length, 1)
|
||
const walk = (dir) => fs.readdirSync(dir, { withFileTypes: true }).flatMap((e) => (e.isDirectory() ? walk(path.join(dir, e.name)) : [path.join(dir, e.name)]))
|
||
const quellen = walk(path.join(ROOT, 'src')).filter((f) => /\.(jsx?|mjs)$/.test(f) && !f.endsWith(path.join('lib', 'hustlerStufe.js')))
|
||
assert.ok(quellen.length > 50)
|
||
for (const f of quellen) assert.ok(!fs.readFileSync(f, 'utf8').includes('VITE_HUSTLER_STUFE'), path.relative(ROOT, f))
|
||
})
|
||
|
||
// ---------- Schwellen ----------
|
||
|
||
test('Schwellen: Antwort-Knöpfe ab Stufe 4, Keller ab Stufe 6; unbrauchbare Stufe gilt als HUSTLER_STUFE', () => {
|
||
for (let n = 1; n <= 9; n += 1) {
|
||
assert.equal(antwortenOffen(n), n >= 4, `antwortenOffen(${n})`)
|
||
assert.equal(kellerOffen(n), n >= 6, `kellerOffen(${n})`)
|
||
}
|
||
assert.deepEqual([antwortenOffen(3), kellerOffen(3)], [false, false])
|
||
assert.deepEqual([antwortenOffen(6), kellerOffen(6)], [true, true])
|
||
for (const kaputt of [undefined, null, 'x', 0, 12, NaN]) {
|
||
assert.equal(antwortenOffen(kaputt), antwortenOffen(HUSTLER_STUFE)); assert.equal(kellerOffen(kaputt), kellerOffen(HUSTLER_STUFE))
|
||
}
|
||
assert.equal(antwortenOffen(), antwortenOffen(HUSTLER_STUFE)); assert.equal(kellerOffen(), kellerOffen(HUSTLER_STUFE))
|
||
})
|
||
|
||
// ---------- Bildschirme und Deeplinks ----------
|
||
|
||
test('tabErlaubt: nur der Keller (botleads) ist gesperrt, und nur unterhalb von Stufe 6', () => {
|
||
for (const s of [1, 3, 4, 5]) {
|
||
assert.equal(tabErlaubt('botleads', s), false, `Stufe ${s}`)
|
||
for (const frei of ['hochhaus', 'freigabe', '', 'unbekannt', undefined]) assert.equal(tabErlaubt(frei, s), true, `${frei} auf Stufe ${s}`)
|
||
}
|
||
for (const s of [6, 7, 9]) assert.equal(tabErlaubt('botleads', s), true, `Stufe ${s}`)
|
||
})
|
||
|
||
test('linkErlaubt: Links in den Keller sind unterhalb von Stufe 6 kein Link, alles andere bleibt unberührt', () => {
|
||
const keller = [
|
||
'/hustler?tab=botleads', '/hustler?tab=botleads&bot=abc123', '/hustler/?tab=botleads', '/Hustler?tab=botleads', '/hustler?bot=x&tab=botleads',
|
||
'/workers?tab=botleads&bot=x', '/leads?tab=botleads', '/hustler?tab=botleads#oben',
|
||
]
|
||
for (const href of keller) {
|
||
assert.equal(linkErlaubt(href, 3), false, `Stufe 3: ${href}`); assert.equal(linkErlaubt(href, 5), false, `Stufe 5: ${href}`)
|
||
assert.equal(linkErlaubt(href, 6), true, `Stufe 6: ${href}`)
|
||
}
|
||
const frei = [
|
||
'/hustler?tab=freigabe', '/hustler', '/hustler?tab=hochhaus', '/hustler?bot=abc123', '/tickets/100001', '/customers/abc', '/other?tab=botleads', '/hustler/anderes?tab=botleads',
|
||
'https://beispiel.de/hustler?tab=botleads', 'http://x.example/?tab=botleads', '//evil.example/hustler?tab=botleads', '/\\evil.example/hustler?tab=botleads', '', 'kein-pfad', null, undefined, 42,
|
||
]
|
||
for (const href of frei) { assert.equal(linkErlaubt(href, 3), true, `Stufe 3: ${String(href)}`); assert.equal(linkErlaubt(href, 6), true, `Stufe 6: ${String(href)}`) }
|
||
})
|