85 lines
12 KiB
JavaScript
85 lines
12 KiB
JavaScript
import test from 'node:test'
|
|
import assert from 'node:assert/strict'
|
|
import { loadModule } from './load.mjs'
|
|
const F = await loadModule('src/lib/workersSettingsForm.js')
|
|
|
|
const S0 = { paused: true, dailyBudgetUsd: 6, monthlyBudgetUsd: 150, warnPct: 80, mailColdPerDay: 3, mailReplyCapPerDay: 15, sendWindowStart: '09:00', sendWindowEnd: '16:00', sendWeekdaysOnly: true, runsPerNight: 1, candidatesPerRun: 10,
|
|
activeRooms: ['netz', 'kleinanzeigen', 'portale', 'foren', 'aerzte', 'handwerk', 'selbst', 'laden'], connectedRooms: [], regions: ['Rheinland-Pfalz'], previewGate: 'erste_n', previewGateN: 10, previewsSent: 4, offerText: 'Gratis-Vorschau …', offerProductKey: 'hosting',
|
|
pultStaleDays: 3, retentionDays: 90, followupDays: 7, previewReminderDays: 5, previewDeleteDays: 60, reportEmail: 'kenso.gri@gmail.com',
|
|
privacyInteressentenLive: false, sendingHalted: false, sendingHaltedReason: '', updatedAt: '2026-10-17T10:00:00Z', updatedBy: 'x' }
|
|
|
|
test('unverändertes Formular ⇒ leerer Patch; schreibgeschützte Felder (auch H4/A5) nie im Patch', () => {
|
|
const f = F.settingsToForm(S0); assert.deepEqual(F.formToPatch(f, S0), {}); assert.equal(F.validateForm(f).ok, true)
|
|
assert.deepEqual(F.formToPatch({ ...f, paused: false, previewsSent: 99, updatedBy: 'y', offerProductKey: 'x', connectedRooms: ['netz'] }, S0), {})
|
|
assert.deepEqual(F.formToPatch({ ...f, privacyInteressentenLive: true, sendingHalted: true, sendingHaltedReason: 'x' }, S0), {}) // die drei neuen Felder: Änderungen ergeben {}
|
|
assert.deepEqual([...F.READONLY_KEYS], ['paused', 'previewsSent', 'updatedAt', 'updatedBy', 'offerProductKey', 'connectedRooms', 'privacyInteressentenLive', 'sendingHalted', 'sendingHaltedReason'])
|
|
const halted = { ...S0, sendingHalted: true, sendingHaltedReason: 'Zwei harte Bounces in den letzten 10 Mails.' } // Notbremse gesetzt: Formular bleibt gültig, Patch leer
|
|
assert.deepEqual(F.formToPatch(F.settingsToForm(halted), halted), {}); assert.equal(F.validateForm(F.settingsToForm(halted)).ok, true)
|
|
assert.deepEqual(F.formToPatch({ ...f, warnPct: '70', sendingHalted: true }, S0), { warnPct: 70 }) // ein echtes Feld nimmt die schreibgeschützten nicht mit
|
|
// optionale Felder fehlen (alte Dienstantwort): Formular gültig, Patch leer, und nichts Schreibgeschütztes im Patch
|
|
const { previewsSent, privacyInteressentenLive, sendingHalted, sendingHaltedReason, ...schmal } = S0
|
|
assert.deepEqual(F.formToPatch(F.settingsToForm(schmal), schmal), {}); assert.equal(F.validateForm(F.settingsToForm(schmal)).ok, true)
|
|
// das Formular zeigt die schreibgeschützten Werte unverändert an (Dialog, P2-T18)
|
|
assert.equal(F.settingsToForm(halted).sendingHaltedReason, 'Zwei harte Bounces in den letzten 10 Mails.'); assert.equal(F.settingsToForm(S0).previewsSent, 4)
|
|
})
|
|
test('Patch enthält nur Geändertes, getypt; Dezimalkomma, Zeilen, Räume', () => {
|
|
const f = { ...F.settingsToForm(S0), dailyBudgetUsd: '12,5', regions: 'Rheinland-Pfalz\n\n Hessen ', activeRooms: S0.activeRooms.filter((r) => r !== 'kleinanzeigen'), previewGate: 'immer' }
|
|
assert.deepEqual(F.formToPatch(f, S0), { dailyBudgetUsd: 12.5, regions: ['Rheinland-Pfalz', 'Hessen'], activeRooms: ['netz', 'portale', 'foren', 'aerzte', 'handwerk', 'selbst', 'laden'], previewGate: 'immer' })
|
|
// Formular zeigt Zahlen als Text, Regionen als Zeilen, Räume als Id-Liste
|
|
const g = F.settingsToForm({ ...S0, regions: ['Rheinland-Pfalz', 'Hessen'] })
|
|
assert.equal(g.dailyBudgetUsd, '6'); assert.equal(g.runsPerNight, '1'); assert.equal(g.regions, 'Rheinland-Pfalz\nHessen'); assert.deepEqual(g.activeRooms, S0.activeRooms); assert.equal(g.sendWeekdaysOnly, true)
|
|
// Räume: Reihenfolge der Eingabe egal, Ausgabe in ROOM_CHOICES-Reihenfolge; nur Umsortieren ist keine Änderung
|
|
assert.deepEqual(F.formToPatch({ ...g, regions: 'Rheinland-Pfalz\nHessen', activeRooms: [...S0.activeRooms].reverse() }, { ...S0, regions: ['Rheinland-Pfalz', 'Hessen'] }), {})
|
|
assert.deepEqual(F.formToPatch({ ...F.settingsToForm(S0), activeRooms: ['laden', 'netz'] }, S0), { activeRooms: ['netz', 'laden'] })
|
|
// Regionen: reihenfolgetreu, Umsortieren ist eine Änderung
|
|
assert.deepEqual(F.formToPatch({ ...g, regions: 'Hessen\nRheinland-Pfalz' }, { ...S0, regions: ['Rheinland-Pfalz', 'Hessen'] }), { regions: ['Hessen', 'Rheinland-Pfalz'] })
|
|
// Schalter, Zeit (mit Leerraum), Text, Adresse: getypt und getrimmt
|
|
assert.deepEqual(F.formToPatch({ ...F.settingsToForm(S0), sendWeekdaysOnly: false, sendWindowEnd: ' 17:30 ', offerText: ' Neuer Satz ', reportEmail: ' a@b.de ', monthlyBudgetUsd: '99.5', mailColdPerDay: '0' }, S0),
|
|
{ sendWeekdaysOnly: false, sendWindowEnd: '17:30', offerText: 'Neuer Satz', reportEmail: 'a@b.de', monthlyBudgetUsd: 99.5, mailColdPerDay: 0 })
|
|
// ungültige Felder gelangen nie in den Patch (kein NaN, kein Text statt Zahl), gültige daneben schon
|
|
assert.deepEqual(F.formToPatch({ ...F.settingsToForm(S0), warnPct: 'x', mailColdPerDay: '4', sendWindowStart: '9:00', regions: ' \n', runsPerNight: '2' }, S0), { runsPerNight: 2 })
|
|
})
|
|
test('Prüfung mit den Spec-Grenzen', () => {
|
|
const bad = (over) => F.validateForm({ ...F.settingsToForm(S0), ...over }).errors
|
|
assert.equal(bad({ mailColdPerDay: '4' }).mailColdPerDay, 'Höchstens 3 unverlangte Mails pro Tag.'); assert.equal(bad({ mailReplyCapPerDay: '16' }).mailReplyCapPerDay, 'Höchstens 15 Antwort-Mails pro Tag.')
|
|
assert.equal(bad({ dailyBudgetUsd: '151' }).dailyBudgetUsd, 'Die Tagesgrenze darf nicht über der Monatsgrenze liegen.'); assert.equal(bad({ monthlyBudgetUsd: '151' }).monthlyBudgetUsd, 'Monatsgrenze höchstens 150 $ (Vorgabe: max. 150 € im Monat).'); assert.equal(bad({ dailyBudgetUsd: '150' }).dailyBudgetUsd, undefined) // Monatsgrenze 150 aus S0
|
|
assert.equal(bad({ dailyBudgetUsd: '0' }).dailyBudgetUsd, 'Tagesgrenze muss größer als 0 sein.')
|
|
assert.equal(bad({ sendWindowStart: '16:00', sendWindowEnd: '09:00' }).sendWindowStart, 'Uhrzeit als HH:MM, Beginn vor Ende.'); assert.equal(bad({ regions: ' \n' }).regions, 'Mindestens eine Region, höchstens 30, je Zeile höchstens 64 Zeichen.')
|
|
assert.equal(bad({ offerText: '' }).offerText, 'Angebotstext: 1 bis 400 Zeichen.'); assert.equal(bad({ offerText: 'x'.repeat(401) }).offerText, 'Angebotstext: 1 bis 400 Zeichen.'); assert.equal(bad({ offerText: 'x'.repeat(400) }).offerText, undefined)
|
|
assert.equal(bad({ retentionDays: '5' }).retentionDays, 'Aufbewahrung: 7 bis 90 Tage.'); assert.equal(bad({ retentionDays: '6' }).retentionDays, 'Aufbewahrung: 7 bis 90 Tage.'); assert.equal(bad({ retentionDays: '91' }).retentionDays, 'Aufbewahrung: 7 bis 90 Tage.')
|
|
assert.equal(bad({ retentionDays: '7' }).retentionDays, undefined); assert.equal(bad({ retentionDays: '90' }).retentionDays, undefined); assert.equal(bad({ warnPct: 'x' }).warnPct, 'Warnschwelle: ganze Zahl von 1 bis 100.')
|
|
assert.equal(bad({ runsPerNight: '3' }).runsPerNight, 'Läufe pro Nacht: 1 oder 2.'); assert.equal(bad({ candidatesPerRun: '21' }).candidatesPerRun, 'Kandidaten pro Lauf: 1 bis 20.')
|
|
assert.equal(F.validateForm(F.settingsToForm(S0)).ok, true); assert.deepEqual(F.ROOM_CHOICES.map((r) => r.id), S0.activeRooms)
|
|
// Uhrzeiten wie im Dienst (P1): genau zweistellig HH:MM, 00:00 bis 23:59
|
|
const T = 'Uhrzeit als HH:MM, Beginn vor Ende.'
|
|
assert.equal(bad({ sendWindowStart: '9:00' }).sendWindowStart, T); assert.equal(bad({ sendWindowEnd: '24:00' }).sendWindowEnd, T)
|
|
assert.equal(bad({ sendWindowEnd: '16:60' }).sendWindowEnd, T); assert.equal(bad({ sendWindowStart: '09:00', sendWindowEnd: '9:30' }).sendWindowEnd, T); assert.equal(bad({ sendWindowStart: '' }).sendWindowStart, T)
|
|
assert.equal(bad({ sendWindowStart: '09:00', sendWindowEnd: '09:00' }).sendWindowStart, T) // gleich ist nicht „vor“
|
|
assert.deepEqual(bad({ sendWindowStart: '00:00', sendWindowEnd: '23:59' }), {})
|
|
// Grenzen der übrigen Zahlen (innen gültig, außen ungültig), Dezimalkomma und Schreibweisen
|
|
assert.equal(bad({ mailColdPerDay: '0' }).mailColdPerDay, undefined); assert.equal(bad({ mailColdPerDay: '-1' }).mailColdPerDay, 'Höchstens 3 unverlangte Mails pro Tag.'); assert.equal(bad({ mailReplyCapPerDay: '15' }).mailReplyCapPerDay, undefined)
|
|
assert.equal(bad({ warnPct: '0' }).warnPct, 'Warnschwelle: ganze Zahl von 1 bis 100.'); assert.equal(bad({ warnPct: '100' }).warnPct, undefined); assert.equal(bad({ warnPct: '101' }).warnPct, 'Warnschwelle: ganze Zahl von 1 bis 100.'); assert.equal(bad({ warnPct: '80,5' }).warnPct, 'Warnschwelle: ganze Zahl von 1 bis 100.')
|
|
assert.equal(bad({ warnPct: '' }).warnPct, 'Warnschwelle: ganze Zahl von 1 bis 100.'); assert.equal(bad({ warnPct: '1e1' }).warnPct, 'Warnschwelle: ganze Zahl von 1 bis 100.'); assert.equal(bad({ runsPerNight: '0' }).runsPerNight, 'Läufe pro Nacht: 1 oder 2.'); assert.equal(bad({ runsPerNight: '2' }).runsPerNight, undefined)
|
|
assert.equal(bad({ candidatesPerRun: '0' }).candidatesPerRun, 'Kandidaten pro Lauf: 1 bis 20.'); assert.equal(bad({ candidatesPerRun: '20' }).candidatesPerRun, undefined)
|
|
assert.ok(bad({ previewGateN: '1001' }).previewGateN); assert.equal(bad({ previewGateN: '1000' }).previewGateN, undefined); assert.equal(bad({ previewGateN: '0' }).previewGateN, undefined)
|
|
assert.ok(bad({ pultStaleDays: '0' }).pultStaleDays); assert.ok(bad({ pultStaleDays: '31' }).pultStaleDays); assert.equal(bad({ pultStaleDays: '30' }).pultStaleDays, undefined)
|
|
assert.ok(bad({ followupDays: '61' }).followupDays); assert.ok(bad({ previewReminderDays: '0' }).previewReminderDays); assert.equal(bad({ previewReminderDays: '60' }).previewReminderDays, undefined)
|
|
assert.ok(bad({ previewDeleteDays: '6' }).previewDeleteDays); assert.ok(bad({ previewDeleteDays: '366' }).previewDeleteDays); assert.equal(bad({ previewDeleteDays: '365' }).previewDeleteDays, undefined); assert.equal(bad({ previewDeleteDays: '7' }).previewDeleteDays, undefined)
|
|
// Budget: beide größer als 0, Monat höchstens 150 (auch bei Komma), Tag nicht über Monat
|
|
assert.equal(bad({ monthlyBudgetUsd: '0' }).monthlyBudgetUsd, 'Monatsgrenze muss größer als 0 sein.'); assert.equal(bad({ monthlyBudgetUsd: 'viel' }).monthlyBudgetUsd, 'Monatsgrenze muss größer als 0 sein.')
|
|
assert.equal(bad({ monthlyBudgetUsd: '150,01' }).monthlyBudgetUsd, 'Monatsgrenze höchstens 150 $ (Vorgabe: max. 150 € im Monat).'); assert.equal(bad({ monthlyBudgetUsd: '150' }).monthlyBudgetUsd, undefined)
|
|
assert.equal(bad({ monthlyBudgetUsd: '5', dailyBudgetUsd: '6' }).dailyBudgetUsd, 'Die Tagesgrenze darf nicht über der Monatsgrenze liegen.'); assert.equal(bad({ monthlyBudgetUsd: '6', dailyBudgetUsd: '6' }).dailyBudgetUsd, undefined)
|
|
assert.equal(bad({ monthlyBudgetUsd: 'x', dailyBudgetUsd: '6' }).dailyBudgetUsd, undefined) // Vergleich nur mit gültiger Monatsgrenze, sonst nur die Meldung am Monatsfeld
|
|
assert.equal(bad({ dailyBudgetUsd: '-1' }).dailyBudgetUsd, 'Tagesgrenze muss größer als 0 sein.'); assert.equal(bad({ dailyBudgetUsd: '0,5' }).dailyBudgetUsd, undefined)
|
|
// Regionen: höchstens 30 Zeilen, je höchstens 64 Zeichen
|
|
const R = 'Mindestens eine Region, höchstens 30, je Zeile höchstens 64 Zeichen.'
|
|
assert.equal(bad({ regions: Array.from({ length: 30 }, (_, i) => `R${i}`).join('\n') }).regions, undefined); assert.equal(bad({ regions: Array.from({ length: 31 }, (_, i) => `R${i}`).join('\n') }).regions, R)
|
|
assert.equal(bad({ regions: 'x'.repeat(64) }).regions, undefined); assert.equal(bad({ regions: 'x'.repeat(65) }).regions, R)
|
|
// Auswahl und Adresse
|
|
assert.ok(bad({ previewGate: 'oft' }).previewGate); assert.equal(bad({ previewGate: 'nie' }).previewGate, undefined); assert.ok(bad({ reportEmail: 'kein-at' }).reportEmail); assert.ok(bad({ reportEmail: '' }).reportEmail); assert.ok(bad({ reportEmail: 'a b@c.de' }).reportEmail)
|
|
// schreibgeschützte Felder werden nie geprüft, auch wenn sie unsinnig sind
|
|
assert.deepEqual(bad({ paused: 'x', previewsSent: 'x', updatedBy: 7, sendingHalted: 'ja', connectedRooms: 'x' }), {})
|
|
// ROOM_CHOICES: die acht Suchräume mit Titel aus ROOM_TITLES, Rollenreihenfolge
|
|
assert.equal(F.ROOM_CHOICES.length, 8); assert.deepEqual(F.ROOM_CHOICES[0], { id: 'netz', title: 'Netz-Radar' }); assert.deepEqual(F.ROOM_CHOICES[7], { id: 'laden', title: 'Läden & Gastro' })
|
|
})
|