Workers P2: Prüfstand mit Skript-API und Playwright-Abnahme

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-18 04:55:51 +00:00
1 parent 0b78379b9b
commit d67f8cc471
6 files changed
+896

No files matched your search

+120
View File
@@ -0,0 +1,120 @@
import test from 'node:test'
import assert from 'node:assert/strict'
import { loadModule } from './load.mjs'
// Prüfstand der Hustler-Seite: die Skript-API (gleiche Form wie createWorkersApi) für die manuelle Playwright-Abnahme.
// Alles mit einer eigenen Uhr: keine echte Zeit, kein Netz.
const { createScriptedApi, SCRIPT } = await loadModule('scripts/workers-harness/scripted-api.js')
const { EVENT_KINDS } = await loadModule('src/lib/workersEvents.js')
const { SEARCH_ROOMS, ROLES, roleOf } = await loadModule('src/lib/workersRoster.js')
const ms = (iso) => Date.parse(iso)
test('Skript deckt alle 26 Ereignisarten und mindestens 15 Worker ab; erstes Ereignis nicht vor 2 s', () => {
assert.deepEqual(new Set(SCRIPT.map((s) => s.event.kind)), new Set(EVENT_KINDS))
assert.ok(new Set(SCRIPT.map((s) => s.event.workerId).filter((w) => w !== 'system')).size >= 15)
assert.ok(Math.min(...SCRIPT.map((s) => s.at)) >= 2)
})
test('scripted api: 19 Worker (kim, pia, finn nicht angeschlossen); events liefert wie der Dienst ab after - 5 s, nie aus der Zukunft; Pause folgt', async () => {
let t = 0
const api = createScriptedApi({ now: () => t })
const s = await api.state()
assert.equal(s.workers.length, 19)
assert.deepEqual(s.workers.filter((w) => !w.connected).map((w) => w.workerId).sort(), ['finn', 'kim', 'pia'])
// Grau-Regel (P1-E9): nur Such-Räume können nicht angeschlossen sein, die anderen elf sind es immer
assert.ok(s.workers.filter((w) => !w.connected).every((w) => SEARCH_ROOMS.includes(roleOf(w.workerId).room)))
assert.deepEqual(s.workers.map((w) => w.workerId), ROLES.map((r) => r.workerId))
assert.ok(new Set(s.workers.map((w) => w.status)).size >= 3) // gemischte Status
const base = ms(s.lastEventTs)
assert.ok(Number.isFinite(base))
assert.equal((await api.events(null)).events.length, 0) // nichts fällig bei 0 s
// PF-09: Antwort nach P1-Semantik (ts >= after - 5000, aufsteigend, nur Fälliges); die Entdopplung übernimmt der Feed (T05)
t = 10000
const first = (await api.events(s.lastEventTs)).events
assert.ok(first.length > 0)
assert.ok(first.every((e) => ms(e.ts) <= base + 10000)) // nichts aus der Zukunft
assert.ok(first.every((e, i) => i === 0 || ms(first[i - 1].ts) <= ms(e.ts))) // aufsteigend
const last = first.at(-1)
const again = (await api.events(last.ts)).events
assert.ok(again.some((e) => e.id === last.id)) // Überlappung: das letzte Ereignis kommt wieder
assert.ok(again.every((e) => ms(e.ts) >= ms(last.ts) - 5000 && ms(e.ts) <= base + 10000))
assert.equal(new Set(again.map((e) => e.id)).size, again.length) // innerhalb einer Antwort nie doppelt
assert.equal((await api.events(null, 2)).events.length, 2) // limit: die ältesten
assert.deepEqual((await api.events(null, 2)).events.map((e) => e.id), first.slice(0, 2).map((e) => e.id))
t = 30000 // später: Neues kommt dazu
const later = (await api.events(last.ts)).events
assert.ok(later.some((e) => !first.some((f) => f.id === e.id)))
assert.ok(later.every((e) => ms(e.ts) <= base + 30000))
await api.pause(true)
assert.equal((await api.state()).paused, true)
await api.pause(false)
assert.equal((await api.state()).paused, false)
assert.equal(s.sendingHalted, false) // Standard: nicht gestoppt
assert.equal(s.sendingHaltedReason, '')
// H4: gestoppt, Grund sichtbar, Freigabe über saveSettings
const h = createScriptedApi({ now: () => 0, halted: true })
const hs = await h.state()
assert.equal(hs.sendingHalted, true)
assert.match(hs.sendingHaltedReason, /Bounces/)
assert.equal((await h.settings()).sendingHalted, true)
const saved = await h.saveSettings({ sendingHalted: false })
assert.equal(saved.sendingHalted, false)
assert.equal(saved.sendingHaltedReason, '')
assert.deepEqual(h.saved, [{ sendingHalted: false }])
assert.equal((await h.state()).sendingHalted, false)
assert.equal((await h.settings()).sendingHalted, false)
assert.equal((await h.settings()).sendingHaltedReason, '')
})
test('scripted api: Spur mit 12 Ereignissen (unsicherer und sicherer Link), Tageswerte, Standard-Einstellungen, Zustand folgt den Ereignissen', async () => {
let t = 0
const api = createScriptedApi({ now: () => t })
const tr = await api.trace('kim')
assert.equal(tr.worker.workerId, 'kim')
assert.equal(tr.worker.name, 'Kim')
assert.equal(tr.worker.modelId, 'claude-haiku-5-5')
assert.equal(tr.events.length, 12)
assert.ok(tr.events.some((e) => e.link === 'javascript:alert(1)'))
assert.ok(tr.events.some((e) => e.link === 'https://example.org/fund'))
assert.ok(tr.events.every((e, i) => i === 0 || ms(tr.events[i - 1].ts) >= ms(e.ts))) // neueste zuerst
assert.ok(Number.isFinite(tr.today.tokens.in) && Number.isFinite(tr.today.tokens.out) && Number.isFinite(tr.today.costUsd))
assert.ok(Object.keys((await api.trace('rita')).today.counters).length > 0)
assert.deepEqual(tr.today.tokens, { in: 18400, out: 2300 })
assert.equal((await api.trace('bob')).worker.modelId, 'claude-opus-5-5')
assert.equal((await api.trace('bea')).worker.modelId, 'claude-sonnet-5-5')
const st = await api.settings() // Standardwerte aus Übersicht §4.1
assert.deepEqual(
[st.dailyBudgetUsd, st.monthlyBudgetUsd, st.warnPct, st.mailColdPerDay, st.mailReplyCapPerDay, st.sendWindowStart, st.sendWindowEnd, st.sendWeekdaysOnly],
[6, 150, 80, 3, 15, '09:00', '16:00', true])
assert.deepEqual([st.runsPerNight, st.candidatesPerRun, st.previewGate, st.previewGateN, st.retentionDays, st.offerProductKey], [1, 10, 'erste_n', 10, 90, 'hosting'])
assert.deepEqual(st.regions, ['Rheinland-Pfalz'])
assert.equal(st.privacyInteressentenLive, false)
assert.equal(st.sendingHalted, false)
assert.equal(st.sendingHaltedReason, '')
assert.ok(st.offerText.length >= 1 && st.offerText.length <= 400)
assert.deepEqual(st.connectedRooms.filter((r) => !SEARCH_ROOMS.includes(r)), []) // nur Such-Räume
assert.ok(!st.connectedRooms.includes('kleinanzeigen') && !st.connectedRooms.includes('portale') && !st.connectedRooms.includes('foren'))
const copy = await api.settings()
copy.dailyBudgetUsd = 99 // Antworten sind Kopien: der Speicher des Prüfstands bleibt heil
assert.equal((await api.settings()).dailyBudgetUsd, 6)
await api.saveSettings({ dailyBudgetUsd: 4 })
assert.equal((await api.settings()).dailyBudgetUsd, 4)
assert.deepEqual(api.saved, [{ dailyBudgetUsd: 4 }])
// Der Zustand schreibt die fälligen Ereignisse fort (wie der Dienst), lastEventTs rückt auf das jüngste fällige Ereignis
const s0 = await api.state()
t = 60000
const s1 = await api.state()
assert.ok(ms(s1.lastEventTs) > ms(s0.lastEventTs))
assert.equal(s1.workers.length, 19)
assert.deepEqual(s1.workers.filter((w) => !w.connected).map((w) => w.workerId).sort(), ['finn', 'kim', 'pia'])
assert.ok(s1.workers.filter((w) => !w.connected).every((w) => w.status === 'grau'))
assert.ok(s1.workers.find((w) => w.workerId === 'otto').task !== s0.workers.find((w) => w.workerId === 'otto').task)
for (const key of ['newAbosMonth', 'mrrNewEur', 'dealsMonth', 'openThreads', 'pultOpen', 'botLeads', 'apiTodayUsd', 'apiMonthUsd', 'dailyBudgetUsd', 'monthlyBudgetUsd']) assert.ok(Number.isFinite(s1.kpis[key]), key)
assert.deepEqual(await api.pult('offen'), { items: [] })
assert.deepEqual(await api.botleads({}), { items: [], total: 0 })
})