Terminplaner: Tagesjob-Meldung, Betriebe-Zaehlung und Bericht
meldeTagesjob meldet am Ende des Tagesjobs ueber den Melder aus A10a2 die Vorfaelle tagesjob (Fehler oder Abbruch), deckel (Loeschdeckel an 3 Tagesjobs in Folge) und betriebe (ab 20 aktiven Betrieben, D8) und merkt letzterTagesjob (nur Zaehlungen). taeglicherJob liefert geloescht und aktiveBetriebe, tagesbericht haengt "n aktive Betriebe" an. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
689a21f9ac
commit
4601c5ef3b
4 files changed
+471
-34
No files matched your search
+162
-2
@@ -6,7 +6,8 @@ import { schemaKeys } from './support/schema.js'
|
||||
import { useTestEnv } from './support/testEnv.js'
|
||||
import { TERMINE_DATETIMES } from '../server/services/termine/schema.js'
|
||||
|
||||
// Betriebspruefung des Terminplaners (Budget, Warteschlange, Anmeldepause) mit Abkuehlzeit fuer die Team-Mail (T20):
|
||||
// Betriebspruefung des Terminplaners (Budget, Warteschlange, Anmeldepause) und Tagesjob-Meldung (Fehler, Loeschdeckel, Zahl der
|
||||
// aktiven Betriebe) mit Abkuehlzeit fuer die Team-Mail (T20):
|
||||
// Nachbau von Appwrite mit Schema, Mails landen in der Senke (ueber einen Fake, der Sendeversuche zaehlt und gezielt
|
||||
// scheitert), alle Zeiten sind feste Parameter.
|
||||
const aw = await startAppwriteMock({
|
||||
@@ -28,7 +29,7 @@ const { deleteDocument } = await import('../server/services/appwriteAdmin.js')
|
||||
const { setMailTransportForTests } = await import('../server/services/mailer.js')
|
||||
const { loadTemplate } = await import('../server/services/mailTemplates.js')
|
||||
const { einreihen, warteAufVersand, warteschlangeAlter, pausiertBis, resetOutboxForTests } = await import('../server/services/termine/outbox.js')
|
||||
const { pruefeBetrieb, meldeVorfall, resetBetriebForTests } = await import('../server/services/termine/betrieb.js')
|
||||
const { pruefeBetrieb, meldeVorfall, meldeTagesjob, letzterTagesjob, zaehleAktiveBetriebe, resetBetriebForTests } = await import('../server/services/termine/betrieb.js')
|
||||
const { minutenJob, resetJobsForTests } = await import('../server/services/termine/jobs.js')
|
||||
after(() => setMailTransportForTests(null))
|
||||
|
||||
@@ -498,3 +499,162 @@ test('minutenJob: eine unlesbare Warteschlange für die Betriebsprüfung ändert
|
||||
assert.equal(ergebnis.verfallen, 0)
|
||||
assert.equal(ergebnis.fehler, 1, 'nur der Schritt Warteschlange zählt; die Betriebsprüfung fügt keinen Fehler hinzu')
|
||||
})
|
||||
|
||||
// ---------------------------------------------------------------- Tagesjob-Meldung (meldeTagesjob)
|
||||
|
||||
const TAG = 24 * STUNDE
|
||||
const tag = (n) => plus(n * TAG)
|
||||
/** Ergebnis eines Tagesjobs, soweit meldeTagesjob es liest. */
|
||||
const tagesErgebnis = (over = {}) => ({ fehler: 0, gedeckelt: { loeschfristen: 0, abgeschaltet: 0, zellen: 0 }, geloescht: 0, aktiveBetriebe: 0, ...over })
|
||||
const AM_DECKEL = { loeschfristen: 1, abgeschaltet: 0, zellen: 0 }
|
||||
const textAllerMails = () => teamMails().map((mail) => `${mail.subject}\n${mail.text}`).join('\n')
|
||||
|
||||
test('meldeTagesjob: Fehler im Tagesjob ⇒ Vorfall tagesjob mit Log und Team-Mail (nur Zahlen); ohne Fehler kein Vorfall', async () => {
|
||||
assert.deepEqual(await meldeTagesjob(tagesErgebnis(), JETZT), [])
|
||||
assert.equal(teamMails().length, 0)
|
||||
assert.equal(betriebLog().length, 0)
|
||||
|
||||
assert.deepEqual(await meldeTagesjob(tagesErgebnis({ fehler: 2 }), plus(MIN)), ['tagesjob'])
|
||||
assert.equal(teamMails().length, 1)
|
||||
assert.equal(teamMails()[0].subject, '[Terminplaner] Tagesjob mit Fehlern')
|
||||
assert.match(teamMails()[0].text, /2 Fehler/)
|
||||
assert.equal(betriebLogMit(/Tagesjob.*2 Fehler/).length, 1)
|
||||
assert.doesNotMatch(textAllerMails(), /@(?!webklar)|\b[bfcpr]_[a-z0-9]{4,}/i, 'weder Adresse noch Kennung in der Meldung')
|
||||
})
|
||||
|
||||
test('meldeTagesjob: ein Abbruch des Tagesjobs ist ein Vorfall tagesjob, auch ohne gezählten Fehler im Ergebnis', async () => {
|
||||
assert.deepEqual(await meldeTagesjob({ abbruch: true, fehler: 1 }, JETZT), ['tagesjob'])
|
||||
assert.equal(teamMails().length, 1)
|
||||
assert.match(teamMails()[0].text, /abgebrochen/)
|
||||
assert.deepEqual(letzterTagesjob(), { at: JETZT.toISOString(), fehler: 1, gedeckelt: 0, geloescht: 0 })
|
||||
})
|
||||
|
||||
test('meldeTagesjob: der Vorfall tagesjob folgt der Abkühlzeit (Log stündlich, Mail erst nach 6 Stunden, am nächsten Tag wieder)', async () => {
|
||||
await meldeTagesjob(tagesErgebnis({ fehler: 1 }), JETZT)
|
||||
await meldeTagesjob(tagesErgebnis({ fehler: 1 }), plus(30 * MIN))
|
||||
assert.equal(teamMails().length, 1)
|
||||
assert.equal(betriebLogMit(/Tagesjob/).length, 1)
|
||||
await meldeTagesjob(tagesErgebnis({ fehler: 1 }), plus(2 * STUNDE))
|
||||
assert.equal(teamMails().length, 1, 'innerhalb der 6 Stunden keine zweite Mail')
|
||||
assert.equal(betriebLogMit(/Tagesjob/).length, 2, 'das Log nach einer Stunde wieder')
|
||||
await meldeTagesjob(tagesErgebnis({ fehler: 1 }), tag(1))
|
||||
assert.equal(teamMails().length, 2, 'der Tagesjob am nächsten Tag meldet wieder')
|
||||
})
|
||||
|
||||
test('meldeTagesjob: Löschdeckel an 2 Tagesjobs in Folge ⇒ kein Vorfall, am 3. ⇒ deckel; die Folge hält an', async () => {
|
||||
const gedeckelt = tagesErgebnis({ gedeckelt: AM_DECKEL })
|
||||
assert.deepEqual(await meldeTagesjob(gedeckelt, tag(0)), [])
|
||||
assert.deepEqual(await meldeTagesjob(gedeckelt, tag(1)), [])
|
||||
assert.equal(teamMails().length, 0)
|
||||
assert.deepEqual(await meldeTagesjob(gedeckelt, tag(2)), ['deckel'])
|
||||
assert.equal(teamMails().length, 1)
|
||||
assert.equal(teamMails()[0].subject, '[Terminplaner] Löschläufe am Deckel')
|
||||
assert.match(teamMails()[0].text, /3 Tagesjobs in Folge/)
|
||||
assert.equal(betriebLogMit(/Deckel/).length, 1)
|
||||
|
||||
assert.deepEqual(await meldeTagesjob(gedeckelt, tag(3)), ['deckel'], 'solange der Rückstand anhält, bleibt es ein Vorfall')
|
||||
assert.equal(teamMails().length, 2, 'am nächsten Tag nach der Abkühlzeit wieder eine Mail')
|
||||
})
|
||||
|
||||
test('meldeTagesjob: ein Tagesjob ohne Deckel unterbricht die Folge; jeder Bereich (Fristen, abgeschaltete Funktionen, Zellen) zählt', async () => {
|
||||
const frei = tagesErgebnis()
|
||||
const mitDeckel = (gedeckelt) => tagesErgebnis({ gedeckelt })
|
||||
assert.deepEqual(await meldeTagesjob(mitDeckel({ loeschfristen: 1, abgeschaltet: 0, zellen: 0 }), tag(0)), [])
|
||||
assert.deepEqual(await meldeTagesjob(mitDeckel({ loeschfristen: 0, abgeschaltet: 3, zellen: 0 }), tag(1)), [])
|
||||
assert.deepEqual(await meldeTagesjob(frei, tag(2)), [])
|
||||
assert.deepEqual(await meldeTagesjob(mitDeckel({ loeschfristen: 0, abgeschaltet: 0, zellen: 1 }), tag(3)), [])
|
||||
assert.deepEqual(await meldeTagesjob(mitDeckel(AM_DECKEL), tag(4)), [])
|
||||
assert.deepEqual(await meldeTagesjob(mitDeckel({ loeschfristen: 2, abgeschaltet: 1, zellen: 1 }), tag(5)), ['deckel'], 'drei Tage in Folge, jeder mit anderem Bereich')
|
||||
assert.equal(teamMails().length, 1)
|
||||
})
|
||||
|
||||
test('meldeTagesjob: ein abgebrochener Tagesjob verändert die Deckel-Folge nicht', async () => {
|
||||
const gedeckelt = tagesErgebnis({ gedeckelt: AM_DECKEL })
|
||||
await meldeTagesjob(gedeckelt, tag(0))
|
||||
await meldeTagesjob(gedeckelt, tag(1))
|
||||
await meldeTagesjob({ abbruch: true, fehler: 1 }, tag(2))
|
||||
assert.deepEqual(await meldeTagesjob(gedeckelt, tag(3)), ['deckel'], 'Abbruch unterbricht die Folge nicht und zählt nicht mit')
|
||||
})
|
||||
|
||||
test('meldeTagesjob: 19 aktive Betriebe ⇒ kein Vorfall, 20 ⇒ betriebe (D8) mit Hinweis auf den Umstieg ab 25; unbekannte Zahl (null) ⇒ nichts', async () => {
|
||||
assert.deepEqual(await meldeTagesjob(tagesErgebnis({ aktiveBetriebe: 19 }), JETZT), [])
|
||||
assert.deepEqual(await meldeTagesjob(tagesErgebnis({ aktiveBetriebe: null }), plus(MIN)), [])
|
||||
assert.equal(teamMails().length, 0)
|
||||
assert.deepEqual(await meldeTagesjob(tagesErgebnis({ aktiveBetriebe: 20 }), plus(2 * MIN)), ['betriebe'])
|
||||
assert.equal(teamMails().length, 1)
|
||||
assert.equal(teamMails()[0].subject, '[Terminplaner] Viele aktive Betriebe')
|
||||
assert.match(teamMails()[0].text, /20 aktive Betriebe/)
|
||||
assert.match(teamMails()[0].text, /25/)
|
||||
assert.equal(betriebLogMit(/20 aktive Betriebe/).length, 1)
|
||||
|
||||
assert.deepEqual(await meldeTagesjob(tagesErgebnis({ aktiveBetriebe: 21 }), plus(3 * MIN)), ['betriebe'])
|
||||
assert.equal(teamMails().length, 1, 'die Abkühlzeit gilt je Vorfall')
|
||||
})
|
||||
|
||||
test('meldeTagesjob: mehrere Vorfälle zugleich ⇒ je eine Meldung mit eigener Überschrift', async () => {
|
||||
const erg = tagesErgebnis({ fehler: 1, gedeckelt: AM_DECKEL, aktiveBetriebe: 22 })
|
||||
assert.deepEqual(await meldeTagesjob(erg, tag(0)), ['tagesjob', 'betriebe'])
|
||||
assert.deepEqual(await meldeTagesjob(erg, tag(1)), ['tagesjob', 'betriebe'])
|
||||
assert.equal(teamMails().length, 4, 'je Tag eine Mail je Vorfall (Abkühlzeit 6 Stunden)')
|
||||
assert.deepEqual(await meldeTagesjob(erg, tag(2)), ['tagesjob', 'deckel', 'betriebe'], 'am dritten Tag kommt deckel dazu')
|
||||
assert.equal(teamMails().length, 7)
|
||||
assert.deepEqual(
|
||||
[...new Set(teamMails().map((mail) => mail.subject))].sort(),
|
||||
['[Terminplaner] Löschläufe am Deckel', '[Terminplaner] Tagesjob mit Fehlern', '[Terminplaner] Viele aktive Betriebe'],
|
||||
)
|
||||
})
|
||||
|
||||
test('letzterTagesjob: null bis zur ersten Meldung, danach nur Zählungen { at, fehler, gedeckelt, geloescht } als Zahlen (gedeckelt = Summe der Bereiche)', async () => {
|
||||
assert.equal(letzterTagesjob(), null)
|
||||
await meldeTagesjob(tagesErgebnis({ fehler: 3, gedeckelt: { loeschfristen: 2, abgeschaltet: 1, zellen: 1 }, geloescht: 17, aktiveBetriebe: 5 }), JETZT)
|
||||
const letzter = letzterTagesjob()
|
||||
assert.deepEqual(letzter, { at: JETZT.toISOString(), fehler: 3, gedeckelt: 4, geloescht: 17 })
|
||||
for (const schluessel of ['fehler', 'gedeckelt', 'geloescht']) assert.equal(typeof letzter[schluessel], 'number', schluessel)
|
||||
letzter.fehler = 99
|
||||
assert.equal(letzterTagesjob().fehler, 3, 'der Aufrufer bekommt eine Kopie')
|
||||
|
||||
await meldeTagesjob(tagesErgebnis({ geloescht: 4 }), plus(TAG))
|
||||
assert.deepEqual(letzterTagesjob(), { at: plus(TAG).toISOString(), fehler: 0, gedeckelt: 0, geloescht: 4 }, 'immer der letzte Lauf')
|
||||
resetBetriebForTests()
|
||||
assert.equal(letzterTagesjob(), null)
|
||||
})
|
||||
|
||||
test('meldeTagesjob wirft nie: scheitert die Team-Mail, steht das im Log, der Lauf ist gemerkt und die Mail wird nicht neu versucht', async () => {
|
||||
tr.wirf = (mail) => (ist20(mail) ? new Error('Verbindung abgelehnt') : null)
|
||||
assert.deepEqual(await meldeTagesjob(tagesErgebnis({ fehler: 1 }), JETZT), ['tagesjob'])
|
||||
assert.equal(tr.teamVersuche, 1)
|
||||
assert.equal(teamMails().length, 0)
|
||||
assert.equal(betriebLogMit(/Team-Mail.*nicht gesendet/).length, 1)
|
||||
assert.equal(letzterTagesjob().fehler, 1)
|
||||
await meldeTagesjob(tagesErgebnis({ fehler: 1 }), plus(MIN))
|
||||
assert.equal(tr.teamVersuche, 1)
|
||||
|
||||
config.chatNotify.emails = []
|
||||
assert.deepEqual(await meldeTagesjob(tagesErgebnis({ aktiveBetriebe: 30 }), plus(2 * MIN)), ['betriebe'], 'ohne Empfänger bleibt das Log')
|
||||
assert.equal(betriebLogMit(/30 aktive Betriebe/).length, 1)
|
||||
})
|
||||
|
||||
test('meldeTagesjob: unbrauchbare Ergebnisse ergeben keinen Wurf und keinen Vorfall; eine ungültige Uhrzeit ist ein TypeError', async () => {
|
||||
for (const unbrauchbar of [undefined, null, {}, 'x', { fehler: 'viele', gedeckelt: 'ja', geloescht: -3, aktiveBetriebe: 'zwanzig' }, { fehler: -1, aktiveBetriebe: 2.5 }]) {
|
||||
assert.deepEqual(await meldeTagesjob(unbrauchbar, JETZT), [], JSON.stringify(unbrauchbar))
|
||||
}
|
||||
assert.equal(teamMails().length, 0)
|
||||
assert.deepEqual(letzterTagesjob(), { at: JETZT.toISOString(), fehler: 0, gedeckelt: 0, geloescht: 0 })
|
||||
await assert.rejects(() => meldeTagesjob(tagesErgebnis(), 'jetzt'), TypeError)
|
||||
})
|
||||
|
||||
test('zaehleAktiveBetriebe: zählt nur Termine-Dokumente im Zustand active (kein Entwurf, nicht abgeschaltet, keine andere Funktion); Appwrite 500 wirft', async () => {
|
||||
const doc = (id, over) => aw.seed('portalFeatures', { $id: id, customerId: 'c_k', projectId: `p_${id}`, featureKey: 'termine', state: 'active', ...over })
|
||||
assert.equal(await zaehleAktiveBetriebe(), 0)
|
||||
doc('f_aktiv01')
|
||||
doc('f_aktiv02')
|
||||
doc('f_aus0001', { state: 'disabled' })
|
||||
doc('f_bestellt', { state: 'ordered' })
|
||||
doc('f_einbau01', { state: 'setup' })
|
||||
doc('f_entwurf1', { state: 'off' })
|
||||
doc('f_news0001', { featureKey: 'news' })
|
||||
assert.equal(await zaehleAktiveBetriebe(), 2)
|
||||
|
||||
aw.failNext({ collection: 'portalFeatures', method: 'GET', status: 500, message: 'Nachbau kaputt' })
|
||||
await assert.rejects(() => zaehleAktiveBetriebe(), (err) => err.status === 500)
|
||||
})
|
||||
Reference in new issue
Block a user