Hustler-Stufe: Keller und Bot-Leads erst ab Stufe 6 (Seite, Keller-Etage, Spur-Links, Abruf von /botleads)

WorkersPage: ?tab=botleads (auch mit &bot=) und unbekannte Werte fallen unterhalb von Stufe 6 auf das Hochhaus zurück, BotLeadList wird nie eingehängt; die Weiterleitungen /leads und /workers bleiben.
HochhausWelt: die Keller-Etage ist dann ein Knopf wie jede andere (zoomt heran) statt eines Links; Arne bleibt klickbar, die Zahl Bot-Leads in der Kopfzeile bleibt Text.
HustlerDialog: Spur-Links in den Keller bleiben Text. workersApi.botleads ruft unterhalb von Stufe 6 nichts ab (kein JWT, keine Anfrage), sondern liefert 404 mit deutschem Satz.
Bestehende Keller-Tests laufen ausdrücklich auf Stufe 6; neue Tests prüfen Stufe 1, 3 und 5 (gesperrt) und 6 bis 9 (offen).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-22 10:36:52 +00:00
1 parent 5e48fbf264
commit 32f4f1d941
10 files changed
+262 -21

No files matched your search

+86 -2
View File
@@ -5,6 +5,7 @@ import { fileURLToPath } from 'node:url'
import esbuild from 'esbuild'
import { fakeReact } from './helpers/fakeReact.mjs'
import { ev, makeState } from './helpers/workersFixtures.mjs'
import { loadModule } from './load.mjs'
// Das Gerüst der Hustler-Pixelwelt (T3) läuft wirklich: im Mini-Läufer anstelle von React (Muster botleadList.test.mjs, Baustein helpers/fakeReact.mjs).
// 1. Hochhaus.jsx: Laden, Dienst nicht erreichbar („Neu laden“), geladen -> Welt, Kopfzeile, Detailansicht auf Klick.
@@ -14,6 +15,7 @@ import { ev, makeState } from './helpers/workersFixtures.mjs'
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..')
const importBundle = (out) => import(`data:text/javascript;base64,${Buffer.from(out.outputFiles[0].text).toString('base64')}`)
const settle = () => new Promise((resolve) => setImmediate(resolve))
const { HUSTLER_STUFE } = await loadModule('src/lib/hustlerStufe.js')
const nodes = (node, match, out = []) => {
if (node === null || typeof node === 'string') return out
@@ -85,6 +87,26 @@ test('Hochhaus: solange nichts geladen ist, steht „Wird geladen …“ mit rol
} finally { view.end() }
})
test('Hochhaus: die Stufe (Stufen-Sperre) geht an die Welt und an die Detailansicht; ohne Angabe gilt HUSTLER_STUFE', async () => {
const feed = { state: makeState(), error: null, loaded: true, refresh() {} }
for (const stufe of [3, 6]) {
const view = await hochhaus(feed, { stufe })
try {
assert.equal(view.hh.welt.stufe, stufe)
view.hh.welt.onZeige('arne')
await settle()
assert.equal(view.hh.dialog.stufe, stufe); assert.equal(view.hh.dialog.workerId, 'arne')
} finally { view.end() }
}
const ohne = await hochhaus(feed)
try {
assert.equal(ohne.hh.welt.stufe, HUSTLER_STUFE)
ohne.hh.welt.onZeige('arne')
await settle()
assert.equal(ohne.hh.dialog.stufe, HUSTLER_STUFE)
} finally { ohne.end() }
})
test('Hochhaus: Dienst nie erreicht -> Pixel-Leerzustand mit Titel, Fehlertext als Text und „Neu laden“, das refresh ruft', async () => {
const c = counting()
const view = await hochhaus({ state: null, error: '<b>Der Dienst antwortet nicht.</b>', loaded: false, refresh: c.refresh })
@@ -246,7 +268,9 @@ const loadSeite = () => (seiteBundle ??= esbuild.build({
}).then(importBundle))
// Ein Lauf der Seite. query ist der Teil nach dem ?, feed wird vom Test zwischendurch geändert (setFeed) und die Seite neu gerendert.
async function seite(query, { admin = false, feed } = {}) {
// stufe (Stufen-Sperre, hustlerStufe.js): die Zusicherungen dieser Datei gelten für die volle Seite (Stufe 6, Keller offen, Verhalten wie vor der Sperre);
// die Tests „Stufe 3“ weiter unten bauen die Seite mit stufe: 3 (Stand der Produktion).
async function seite(query, { admin = false, feed, stufe = 6 } = {}) {
const { __mount, jsx, WorkersPage } = await loadSeite()
const setParamsCalls = []
const hk = {
@@ -263,7 +287,7 @@ async function seite(query, { admin = false, feed } = {}) {
const savedDocument = globalThis.document
globalThis.__hk = hk
globalThis.document = { title: 'Vorher' }
const mounted = __mount(jsx(WorkersPage, {}))
const mounted = __mount(jsx(WorkersPage, { stufe }))
await settle()
const view = {
hk, setParamsCalls,
@@ -306,6 +330,66 @@ test('Seite: ?tab=freigabe zeigt das Pult, ?tab=botleads den Keller, beide direk
} finally { keller.end() }
})
// ---------- Stufen-Sperre (Stufe 3 = Stand der Produktion: Dienst P3, ohne Keller) ----------
// Adressen, die auf Stufe 3 in den Keller führen würden: der Reiter, mit Einzelfall, mit Ballast, andere Schreibweisen und erfundene Parameter
const KELLER_ADRESSEN = ['tab=botleads', 'tab=botleads&bot=abc123', 'bot=abc123&tab=botleads', 'tab=botleads&bot=', 'tab=BotLeads', 'ansicht=keller', 'tab=keller', 'tab=botleads&ansicht=keller']
test('Stufe 3: jede Adresse in den Keller fällt auf das Hochhaus zurück; der Keller wird nie eingehängt, Hochhaus und Seite bleiben ganz', async () => {
for (const stufe of [1, 3, 5]) {
for (const query of KELLER_ADRESSEN) {
const ui = await seite(query, { stufe, feed: { state: makeState(), error: null, loaded: true, refresh() {} } })
try {
assert.equal(ui.root().props.className, 'hk-seite', `${stufe} ${query}`)
assert.equal(ui.root().props['data-tab'], 'hochhaus', `${stufe} ${query}`)
assert.deepEqual(ui.screen(), ['hochhaus'], `${stufe} ${query}`)
assert.equal(ui.hk.renders.keller.length, 0, `${stufe} ${query}: BotLeadList wurde nie gezeichnet`)
assert.equal(ui.hk.props.keller, undefined, `${stufe} ${query}`)
assert.equal(ui.hk.props.hochhaus.stufe, stufe)
} finally { ui.end() }
}
}
})
test('Stufe 3: der Wechsel der Adresse zu ?tab=botleads zeigt das Hochhaus, Freigabe bleibt erreichbar; der Rückweg aus dem Pult funktioniert wie sonst', async () => {
const ui = await seite('tab=freigabe', { stufe: 3, feed: { state: makeState(), error: null, loaded: true, refresh() {} } })
try {
assert.deepEqual(ui.screen(), ['pult'])
ui.go('tab=botleads&bot=x'); assert.deepEqual(ui.screen(), ['hochhaus']); assert.equal(ui.hk.props.hochhaus.vonTab, 'freigabe')
ui.go('tab=freigabe'); assert.deepEqual(ui.screen(), ['pult'])
ui.go('tab=hochhaus'); assert.deepEqual(ui.screen(), ['hochhaus'])
assert.equal(ui.hk.renders.keller.length, 0); assert.equal(ui.hk.props.keller, undefined)
} finally { ui.end() }
})
test('Stufe 3: das Hochhaus öffnet nur noch die Freigabe (onOpen(\'botleads\') tut nichts); Hochhaus und Pult bekommen die Stufe', async () => {
const ui = await seite('tab=hochhaus', { stufe: 3, feed: { state: makeState(), error: null, loaded: true, refresh() {} } })
try {
const p = ui.hk.props.hochhaus
p.onOpen('botleads'); p.onOpen('freigabe')
assert.deepEqual(ui.setParamsCalls, [{ tab: 'freigabe' }])
ui.go('tab=freigabe')
assert.equal(ui.hk.props.pult.stufe, 3)
ui.hk.props.pult.onBack()
assert.deepEqual(ui.setParamsCalls, [{ tab: 'freigabe' }, { tab: 'hochhaus' }])
} finally { ui.end() }
})
test('Stufe 6 und höher: ?tab=botleads zeigt den Keller samt Einzelfall (bot), onOpen(\'botleads\') wechselt; Stufe 4 und 5 haben den Keller noch nicht', async () => {
for (const stufe of [6, 8, 9]) {
const ui = await seite('tab=botleads&bot=abc123', { stufe, feed: { state: makeState(), error: null, loaded: true, refresh() {} } })
try {
assert.deepEqual(ui.screen(), ['keller'], `Stufe ${stufe}`); assert.equal(ui.hk.props.keller.bot, 'abc123')
ui.go('tab=hochhaus'); ui.hk.props.hochhaus.onOpen('botleads')
assert.deepEqual(ui.setParamsCalls, [{ tab: 'botleads' }], `Stufe ${stufe}`)
} finally { ui.end() }
}
for (const stufe of [4, 5]) {
const ui = await seite('tab=botleads', { stufe })
try { assert.deepEqual(ui.screen(), ['hochhaus'], `Stufe ${stufe}`) } finally { ui.end() }
}
})
test('Seite: keine Seitenüberschrift, keine Reiterleiste', async () => {
for (const query of ['', 'tab=freigabe', 'tab=botleads']) {
const ui = await seite(query)