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:
1 parent
0b78379b9b
commit
d67f8cc471
6 files changed
+896
No files matched your search
@@ -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 })
|
||||
})
|
||||
Reference in new issue
Block a user