Terminplaner: Minutenjob in Verfall und Mailversand getrennt geschuetzt
fuehreAus nimmt { teil } und sperrt je job:teil; minutenJob startet Verfall
und Mailversand (Warteschlange plus Betriebspruefung) gemeinsam mit
Promise.all. Haengt ein Teil, laeuft der andere im naechsten Takt weiter
(verfallUebersprungen bzw. mailUebersprungen); uebersprungen gilt nur, wenn
beide Teile uebersprungen werden. Die Ueberlappungswarnung nennt den Teil,
das Fehlerlog behaelt minutenJob.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
aaed273b62
commit
e2f50407a0
2 files changed
+190
-40
No files matched your search
@@ -27,8 +27,10 @@ import { gibZellenFrei, raeumeVerwaisteZellen } from './zellen.js'
|
|||||||
* Hintergrund-Jobs des Terminplaners (Spezifikation 2026-10-02-terminplaner-design.md, Abschnitte 4 und 11). server/index.js
|
* Hintergrund-Jobs des Terminplaners (Spezifikation 2026-10-02-terminplaner-design.md, Abschnitte 4 und 11). server/index.js
|
||||||
* ruft sie mit new Date() auf; `now` ist hier immer ein Parameter.
|
* ruft sie mit new Date() auf; `now` ist hier immer ein Parameter.
|
||||||
*
|
*
|
||||||
* minutenJob jede Minute abgelaufene Haltungen => verfallen (Zellen frei); Mail-Warteschlange senden; zuletzt die
|
* minutenJob jede Minute zwei Teile, gemeinsam gestartet (Promise.all) und je Teil gegen Ueberlappung geschuetzt:
|
||||||
* Betriebspruefung (Budget, Warteschlange, Anmeldepause => Log und Team-Mail, betrieb.js)
|
* Verfall: abgelaufene Haltungen => verfallen (Zellen frei);
|
||||||
|
* Mailversand: Mail-Warteschlange senden, zuletzt die Betriebspruefung (Budget, Warteschlange,
|
||||||
|
* Anmeldepause => Log und Team-Mail, betrieb.js)
|
||||||
* fuenfMinutenJob alle 5 Minuten Erinnerungen (G05); bestaetigt 1 Stunde nach Ende => erledigt; Anfragen mit Beginn in
|
* fuenfMinutenJob alle 5 Minuten Erinnerungen (G05); bestaetigt 1 Stunde nach Ende => erledigt; Anfragen mit Beginn in
|
||||||
* der Vergangenheit => abgelehnt (vom System, Zellen frei, keine Mail)
|
* der Vergangenheit => abgelehnt (vom System, Zellen frei, keine Mail)
|
||||||
* taeglicherJob taeglich Loeschfristen (11), festgefahrene und alte Mails, abgeschaltete Funktionen, verwaiste Zellen;
|
* taeglicherJob taeglich Loeschfristen (11), festgefahrene und alte Mails, abgeschaltete Funktionen, verwaiste Zellen;
|
||||||
@@ -38,7 +40,10 @@ import { gibZellenFrei, raeumeVerwaisteZellen } from './zellen.js'
|
|||||||
* Gemeinsame Regeln:
|
* Gemeinsame Regeln:
|
||||||
* - Modus off: jeder Job kehrt sofort zurueck, ohne Abfrage, mit { aus: true }.
|
* - Modus off: jeder Job kehrt sofort zurueck, ohne Abfrage, mit { aus: true }.
|
||||||
* - Keine ueberlappenden Laeufe: laeuft ein Job noch, wird der naechste Takt uebersprungen ({ uebersprungen: true }, einmal im
|
* - Keine ueberlappenden Laeufe: laeuft ein Job noch, wird der naechste Takt uebersprungen ({ uebersprungen: true }, einmal im
|
||||||
* Log je Ueberlappung).
|
* Log je Ueberlappung). Die Sperre gilt je `job:teil` (fuehreAus mit { teil }): haengt beim Minutenjob der Verfall, versendet der
|
||||||
|
* naechste Takt trotzdem die Warteschlange und umgekehrt; uebersprungen gilt der Takt erst, wenn beide Teile uebersprungen sind
|
||||||
|
* (sonst steht verfallUebersprungen bzw. mailUebersprungen im Ergebnis). Die Warnung nennt den Teil (`minutenJob (Verfall)`), das
|
||||||
|
* Fehlerlog behaelt `minutenJob` als Jobnamen.
|
||||||
* - Ein Schritt eines Jobs scheitert nie den ganzen Job: der Fehler steht im Log (Text des Fehlers, bei einer Buchung mit
|
* - Ein Schritt eines Jobs scheitert nie den ganzen Job: der Fehler steht im Log (Text des Fehlers, bei einer Buchung mit
|
||||||
* Buchungsnummer, nie mit Gastdaten), `fehler` im Ergebnis zaehlt ihn, die uebrigen Schritte laufen. Eine fehlende Collection
|
* Buchungsnummer, nie mit Gastdaten), `fehler` im Ergebnis zaehlt ihn, die uebrigen Schritte laufen. Eine fehlende Collection
|
||||||
* (404 collection_not_found, Einrichtung unvollstaendig) meldet ein Job hoechstens einmal je Stunde.
|
* (404 collection_not_found, Einrichtung unvollstaendig) meldet ein Job hoechstens einmal je Stunde.
|
||||||
@@ -100,23 +105,26 @@ async function schritt(lauf, was, fn, ersatz) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Rahmen jedes Jobs: Schalter, Ueberlappung, Laufdaten. => Ergebnis von arbeit(lauf) | { aus: true } | { uebersprungen: true }
|
* Rahmen jedes Jobs: Schalter, Ueberlappung, Laufdaten. => Ergebnis von arbeit(lauf) | { aus: true } | { uebersprungen: true }
|
||||||
|
* `teil` (Name eines Teiljobs, z. B. „Verfall“) gibt dem Teil eine eigene Ueberlappungssperre `job:teil` und nennt ihn in der
|
||||||
|
* Warnung (`job (teil)`); `lauf.job` bleibt der Jobname (Fehlerlog, Warnung bei fehlender Collection).
|
||||||
*/
|
*/
|
||||||
async function fuehreAus(job, now, arbeit) {
|
async function fuehreAus(job, now, arbeit, { teil = null } = {}) {
|
||||||
const nowMs = pruefeNow(now, job)
|
const nowMs = pruefeNow(now, job)
|
||||||
if (termineAus()) return { aus: true }
|
if (termineAus()) return { aus: true }
|
||||||
if (laufend.has(job)) {
|
const sperre = teil === null ? job : `${job}:${teil}`
|
||||||
if (!ueberlappGemeldet.has(job)) {
|
if (laufend.has(sperre)) {
|
||||||
ueberlappGemeldet.add(job)
|
if (!ueberlappGemeldet.has(sperre)) {
|
||||||
console.warn(`[termine-jobs] ${job}: der vorige Lauf ist noch nicht fertig, dieser Takt wird übersprungen`)
|
ueberlappGemeldet.add(sperre)
|
||||||
|
console.warn(`[termine-jobs] ${teil === null ? job : `${job} (${teil})`}: der vorige Lauf ist noch nicht fertig, dieser Takt wird übersprungen`)
|
||||||
}
|
}
|
||||||
return { uebersprungen: true }
|
return { uebersprungen: true }
|
||||||
}
|
}
|
||||||
laufend.add(job)
|
laufend.add(sperre)
|
||||||
try {
|
try {
|
||||||
return await arbeit({ job, nowMs, fehler: 0, gedeckelt: 0 })
|
return await arbeit({ job, nowMs, fehler: 0, gedeckelt: 0 })
|
||||||
} finally {
|
} finally {
|
||||||
laufend.delete(job)
|
laufend.delete(sperre)
|
||||||
ueberlappGemeldet.delete(job)
|
ueberlappGemeldet.delete(sperre)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,19 +222,48 @@ async function verfalleHaltungen(lauf, now) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Jede Minute: abgelaufene Haltungen verfallen lassen, Warteschlange senden, zuletzt den Betrieb pruefen (Vorfaelle stehen im Log und
|
* Jede Minute, in zwei unabhaengig geschuetzten Teilen, die gemeinsam starten (Promise.all):
|
||||||
* gehen als T20 ans Team; die Pruefung wirft nie und aendert das Ergebnis des Laufs nicht).
|
* - Verfall: abgelaufene Haltungen verfallen lassen;
|
||||||
|
* - Mailversand: Warteschlange senden, zuletzt den Betrieb pruefen (Vorfaelle stehen im Log und gehen als T20 ans Team; die Pruefung
|
||||||
|
* wirft nie und aendert das Ergebnis des Laufs nicht).
|
||||||
|
* Haengt ein Teil, wird nur dieser im naechsten Takt uebersprungen (verfallUebersprungen bzw. mailUebersprungen im Ergebnis), der
|
||||||
|
* andere laeuft weiter. { uebersprungen: true } kommt erst, wenn beide Teile uebersprungen werden.
|
||||||
* @param {Date} now
|
* @param {Date} now
|
||||||
* @returns {Promise<{ verfallen: number, faellig: number, gesendet: number, fehler: number } | { aus: true } | { uebersprungen: true }>}
|
* @returns {Promise<{ verfallen: number, faellig: number, gesendet: number, fehler: number, verfallUebersprungen?: true, mailUebersprungen?: true } | { aus: true } | { uebersprungen: true }>}
|
||||||
*/
|
*/
|
||||||
export async function minutenJob(now) {
|
export async function minutenJob(now) {
|
||||||
return fuehreAus('minutenJob', now, async (lauf) => {
|
const [verfall, mail] = await Promise.all([
|
||||||
const verfallen = await schritt(lauf, 'Verfall', () => verfalleHaltungen(lauf, now), 0)
|
fuehreAus(
|
||||||
const { faellig, gesendet } = await schritt(lauf, 'Warteschlange', () => verarbeite(now), { faellig: 0, gesendet: 0 })
|
'minutenJob',
|
||||||
// Zuletzt: die Pruefung sieht den Stand nach Verfall und Versand (ein eben gesendeter Eintrag staut nichts mehr)
|
now,
|
||||||
await schritt(lauf, 'Betriebsprüfung', () => pruefeBetrieb(now), [])
|
async (lauf) => {
|
||||||
return { verfallen, faellig, gesendet, fehler: lauf.fehler }
|
const verfallen = await schritt(lauf, 'Verfall', () => verfalleHaltungen(lauf, now), 0)
|
||||||
})
|
return { verfallen, fehler: lauf.fehler }
|
||||||
|
},
|
||||||
|
{ teil: 'Verfall' },
|
||||||
|
),
|
||||||
|
fuehreAus(
|
||||||
|
'minutenJob',
|
||||||
|
now,
|
||||||
|
async (lauf) => {
|
||||||
|
const { faellig, gesendet } = await schritt(lauf, 'Warteschlange', () => verarbeite(now), { faellig: 0, gesendet: 0 })
|
||||||
|
// Zuletzt: die Pruefung sieht den Stand nach dem Versand (ein eben gesendeter Eintrag staut nichts mehr)
|
||||||
|
await schritt(lauf, 'Betriebsprüfung', () => pruefeBetrieb(now), [])
|
||||||
|
return { faellig, gesendet, fehler: lauf.fehler }
|
||||||
|
},
|
||||||
|
{ teil: 'Mailversand' },
|
||||||
|
),
|
||||||
|
])
|
||||||
|
if (verfall.aus || mail.aus) return { aus: true }
|
||||||
|
if (verfall.uebersprungen && mail.uebersprungen) return { uebersprungen: true }
|
||||||
|
return {
|
||||||
|
verfallen: verfall.verfallen ?? 0,
|
||||||
|
faellig: mail.faellig ?? 0,
|
||||||
|
gesendet: mail.gesendet ?? 0,
|
||||||
|
fehler: (verfall.fehler ?? 0) + (mail.fehler ?? 0),
|
||||||
|
...(verfall.uebersprungen ? { verfallUebersprungen: true } : {}),
|
||||||
|
...(mail.uebersprungen ? { mailUebersprungen: true } : {}),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------- Fuenf-Minuten-Job
|
// ---------------------------------------------------------------- Fuenf-Minuten-Job
|
||||||
|
|||||||
+132
-19
@@ -264,6 +264,54 @@ async function mitEinwurf(passt, dazwischen, fn) {
|
|||||||
}
|
}
|
||||||
const listeVonBuchungen = (muster) => (url, methode) =>
|
const listeVonBuchungen = (muster) => (url, methode) =>
|
||||||
methode === 'GET' && /\/collections\/termineBookings\/documents(\?|$)/.test(url) && muster.test(url)
|
methode === 'GET' && /\/collections\/termineBookings\/documents(\?|$)/.test(url) && muster.test(url)
|
||||||
|
const listeVonOutbox = (url, methode) => methode === 'GET' && /\/collections\/termineOutbox\/documents(\?|$)/.test(url)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Haengende Schritte: haelt je Muster (url, methode) => boolean die erste Anfrage an, auf die es zutrifft, bis freigeben() alle
|
||||||
|
* loslaesst. warteBisRuhig() wartet, bis keine andere Anfrage mehr laeuft und 50 ms lang keine neue kam (die uebrigen Schritte
|
||||||
|
* des Takts sind dann durch). ende() stellt fetch wieder her.
|
||||||
|
*/
|
||||||
|
function mitHaengern(...muster) {
|
||||||
|
const echt = globalThis.fetch
|
||||||
|
const tore = muster.map(() => {
|
||||||
|
const tor = { gehalten: false }
|
||||||
|
tor.warte = new Promise((resolve) => (tor.los = resolve))
|
||||||
|
return tor
|
||||||
|
})
|
||||||
|
let offen = 0
|
||||||
|
let anfragen = 0
|
||||||
|
globalThis.fetch = async (url, optionen) => {
|
||||||
|
anfragen += 1
|
||||||
|
const text = decodeURIComponent(String(url))
|
||||||
|
const methode = optionen?.method ?? 'GET'
|
||||||
|
const i = muster.findIndex((passt, k) => !tore[k].gehalten && passt(text, methode))
|
||||||
|
if (i >= 0) {
|
||||||
|
tore[i].gehalten = true
|
||||||
|
await tore[i].warte
|
||||||
|
return echt(url, optionen)
|
||||||
|
}
|
||||||
|
offen += 1
|
||||||
|
try {
|
||||||
|
return await echt(url, optionen)
|
||||||
|
} finally {
|
||||||
|
offen -= 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
freigeben: () => tore.forEach((tor) => tor.los()),
|
||||||
|
gehalten: () => tore.every((tor) => tor.gehalten),
|
||||||
|
async warteBisRuhig() {
|
||||||
|
for (let ruhig = 0; ruhig < 5; ) {
|
||||||
|
const stand = anfragen
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 10))
|
||||||
|
ruhig = offen === 0 && stand === anfragen ? ruhig + 1 : 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ende: () => {
|
||||||
|
globalThis.fetch = echt
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Alle Lese-Anfragen an die Collection scheitern wie in Appwrite mit 404 collection_not_found. */
|
/** Alle Lese-Anfragen an die Collection scheitern wie in Appwrite mit 404 collection_not_found. */
|
||||||
async function mitFehlenderCollection(collection, fn) {
|
async function mitFehlenderCollection(collection, fn) {
|
||||||
@@ -1974,46 +2022,111 @@ test('fehlende Collection der Warteschlange: der Minutenjob verfällt trotzdem H
|
|||||||
assert.equal(warnLog().length, 1)
|
assert.equal(warnLog().length, 1)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('überlappende Läufe: ein langsamer Lauf wird vom nächsten Takt übersprungen (einmal im Log), danach geht es normal weiter', async () => {
|
test('überlappende Läufe: hängen beide Teile des Minutenjobs, werden die nächsten Takte übersprungen (je Teil einmal im Log), danach geht es normal weiter', async () => {
|
||||||
await legeBuchung('b_vorbei', { status: 'gehalten', heldUntil: iso(plus(JETZT, -MIN)) })
|
await legeBuchung('b_vorbei', { status: 'gehalten', heldUntil: iso(plus(JETZT, -MIN)) })
|
||||||
const echt = globalThis.fetch
|
const haenger = mitHaengern(listeVonBuchungen(/heldUntil/), listeVonOutbox)
|
||||||
let freigeben
|
|
||||||
const tor = new Promise((resolve) => (freigeben = resolve))
|
|
||||||
let erste = true
|
|
||||||
globalThis.fetch = async (url, optionen) => {
|
|
||||||
if (erste) {
|
|
||||||
erste = false
|
|
||||||
await tor
|
|
||||||
}
|
|
||||||
return echt(url, optionen)
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
const langsam = minutenJob(JETZT)
|
const langsam = minutenJob(JETZT)
|
||||||
|
await haenger.warteBisRuhig()
|
||||||
|
assert.ok(haenger.gehalten(), 'Verfall und Mailversand hängen')
|
||||||
assert.deepEqual(await minutenJob(plus(JETZT, MIN)), { uebersprungen: true })
|
assert.deepEqual(await minutenJob(plus(JETZT, MIN)), { uebersprungen: true })
|
||||||
assert.deepEqual(await minutenJob(plus(JETZT, 2 * MIN)), { uebersprungen: true })
|
assert.deepEqual(await minutenJob(plus(JETZT, 2 * MIN)), { uebersprungen: true })
|
||||||
assert.equal(warnLog().filter((zeile) => /minutenJob/.test(zeile)).length, 1, 'nur einmal gemeldet')
|
assert.equal(warnLog().filter((zeile) => /minutenJob \(Verfall\)/.test(zeile)).length, 1, 'Verfall nur einmal gemeldet')
|
||||||
|
assert.equal(warnLog().filter((zeile) => /minutenJob \(Mailversand\)/.test(zeile)).length, 1, 'Mailversand nur einmal gemeldet')
|
||||||
|
assert.equal(warnLog().filter((zeile) => /minutenJob/.test(zeile)).length, 2, 'sonst keine Warnung')
|
||||||
// andere Jobs sind davon nicht betroffen
|
// andere Jobs sind davon nicht betroffen
|
||||||
assert.equal((await fuenfMinutenJob(JETZT)).uebersprungen, undefined)
|
assert.equal((await fuenfMinutenJob(JETZT)).uebersprungen, undefined)
|
||||||
freigeben()
|
haenger.freigeben()
|
||||||
assert.equal((await langsam).verfallen, 1)
|
assert.equal((await langsam).verfallen, 1)
|
||||||
} finally {
|
} finally {
|
||||||
globalThis.fetch = echt
|
haenger.freigeben()
|
||||||
|
haenger.ende()
|
||||||
}
|
}
|
||||||
assert.equal((await minutenJob(plus(JETZT, 3 * MIN))).uebersprungen, undefined, 'danach läuft der Job wieder')
|
assert.deepEqual(await minutenJob(plus(JETZT, 3 * MIN)), { verfallen: 0, faellig: 0, gesendet: 0, fehler: 0 }, 'danach laufen beide Teile wieder')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('ein Job, der mit einem Fehler endet, gibt die Überlappungssperre frei', async () => {
|
test('überlappende Läufe: hängt der Verfall, versendet der nächste Takt trotzdem die Warteschlange (Überlappung einmal im Log)', async () => {
|
||||||
|
await legeBuchung('b_vorbei', { status: 'gehalten', heldUntil: iso(plus(JETZT, -MIN)) })
|
||||||
|
legeEintrag('o_erst')
|
||||||
|
const haenger = mitHaengern(listeVonBuchungen(/heldUntil/))
|
||||||
|
try {
|
||||||
|
const langsam = minutenJob(JETZT)
|
||||||
|
await haenger.warteBisRuhig()
|
||||||
|
assert.ok(haenger.gehalten(), 'der Verfall hängt')
|
||||||
|
// beide Teile starten gemeinsam: die Warteschlange ist abgearbeitet, obwohl der Verfall des ersten Takts noch hängt
|
||||||
|
assert.equal(aw.docs('termineOutbox').get('o_erst').status, 'gesendet')
|
||||||
|
assert.equal(status('b_vorbei'), 'gehalten')
|
||||||
|
|
||||||
|
legeEintrag('o_zweit')
|
||||||
|
const zweiter = await minutenJob(plus(JETZT, MIN))
|
||||||
|
assert.deepEqual(zweiter, { verfallen: 0, faellig: 1, gesendet: 1, fehler: 0, verfallUebersprungen: true })
|
||||||
|
assert.equal(aw.docs('termineOutbox').get('o_zweit').status, 'gesendet')
|
||||||
|
assert.equal(sink.sent.length, 2)
|
||||||
|
const dritter = await minutenJob(plus(JETZT, 2 * MIN))
|
||||||
|
assert.equal(dritter.verfallUebersprungen, true)
|
||||||
|
assert.equal(dritter.uebersprungen, undefined, 'nur ein Teil übersprungen: der Takt gilt nicht als übersprungen')
|
||||||
|
assert.equal(warnLog().filter((zeile) => /minutenJob \(Verfall\)/.test(zeile)).length, 1, 'die Überlappung steht einmal im Log')
|
||||||
|
assert.equal(warnLog().filter((zeile) => /minutenJob/.test(zeile)).length, 1, 'der Mailversand wurde nicht übersprungen')
|
||||||
|
|
||||||
|
haenger.freigeben()
|
||||||
|
assert.deepEqual(await langsam, { verfallen: 1, faellig: 1, gesendet: 1, fehler: 0 })
|
||||||
|
} finally {
|
||||||
|
haenger.freigeben()
|
||||||
|
haenger.ende()
|
||||||
|
}
|
||||||
|
assert.equal(status('b_vorbei'), 'verfallen')
|
||||||
|
assert.deepEqual(await minutenJob(plus(JETZT, 3 * MIN)), { verfallen: 0, faellig: 0, gesendet: 0, fehler: 0 })
|
||||||
|
})
|
||||||
|
|
||||||
|
test('überlappende Läufe: hängt der Mailversand, verfällt im nächsten Takt eine abgelaufene Haltung trotzdem (Überlappung einmal im Log)', async () => {
|
||||||
|
const haenger = mitHaengern(listeVonOutbox)
|
||||||
|
try {
|
||||||
|
const langsam = minutenJob(JETZT)
|
||||||
|
await haenger.warteBisRuhig()
|
||||||
|
assert.ok(haenger.gehalten(), 'der Mailversand hängt')
|
||||||
|
|
||||||
|
await legeBuchung('b_vorbei', { status: 'gehalten', heldUntil: iso(plus(JETZT, MIN)) })
|
||||||
|
assert.ok(claims('b_vorbei').length > 0)
|
||||||
|
const zweiter = await minutenJob(plus(JETZT, 2 * MIN))
|
||||||
|
assert.deepEqual(zweiter, { verfallen: 1, faellig: 0, gesendet: 0, fehler: 0, mailUebersprungen: true })
|
||||||
|
assert.equal(status('b_vorbei'), 'verfallen')
|
||||||
|
assert.equal(claims('b_vorbei').length, 0, 'Zellen frei')
|
||||||
|
const dritter = await minutenJob(plus(JETZT, 3 * MIN))
|
||||||
|
assert.equal(dritter.mailUebersprungen, true)
|
||||||
|
assert.equal(dritter.uebersprungen, undefined)
|
||||||
|
assert.equal(warnLog().filter((zeile) => /minutenJob \(Mailversand\)/.test(zeile)).length, 1, 'die Überlappung steht einmal im Log')
|
||||||
|
assert.equal(warnLog().filter((zeile) => /minutenJob/.test(zeile)).length, 1, 'der Verfall wurde nicht übersprungen')
|
||||||
|
|
||||||
|
haenger.freigeben()
|
||||||
|
assert.deepEqual(await langsam, { verfallen: 0, faellig: 0, gesendet: 0, fehler: 0 })
|
||||||
|
} finally {
|
||||||
|
haenger.freigeben()
|
||||||
|
haenger.ende()
|
||||||
|
}
|
||||||
|
assert.deepEqual(await minutenJob(plus(JETZT, 4 * MIN)), { verfallen: 0, faellig: 0, gesendet: 0, fehler: 0 })
|
||||||
|
})
|
||||||
|
|
||||||
|
test('ein Job, der mit einem Fehler endet, gibt die Überlappungssperren frei', async () => {
|
||||||
const echt = globalThis.fetch
|
const echt = globalThis.fetch
|
||||||
globalThis.fetch = async () => {
|
globalThis.fetch = async () => {
|
||||||
throw new TypeError('fetch failed')
|
throw new TypeError('fetch failed')
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const erg = await minutenJob(JETZT)
|
const erg = await minutenJob(JETZT)
|
||||||
assert.equal(erg.fehler, 2, 'Verfall und Warteschlange scheitern, der Job wirft nicht')
|
assert.equal(erg.fehler, 2, 'Verfall und Warteschlange scheitern (je Teil ein Fehler), der Job wirft nicht')
|
||||||
|
assert.deepEqual(
|
||||||
|
fehlerLog()
|
||||||
|
.map((zeile) => /^\[termine-jobs\] minutenJob: (Verfall|Warteschlange):/.exec(zeile)?.[1])
|
||||||
|
.filter(Boolean)
|
||||||
|
.sort(),
|
||||||
|
['Verfall', 'Warteschlange'],
|
||||||
|
'das Fehlerlog behält minutenJob als Jobnamen',
|
||||||
|
)
|
||||||
} finally {
|
} finally {
|
||||||
globalThis.fetch = echt
|
globalThis.fetch = echt
|
||||||
}
|
}
|
||||||
assert.equal((await minutenJob(JETZT)).uebersprungen, undefined)
|
assert.deepEqual(await minutenJob(JETZT), { verfallen: 0, faellig: 0, gesendet: 0, fehler: 0 }, 'beide Sperren sind frei')
|
||||||
|
assert.equal(warnLog().filter((zeile) => /nicht fertig/.test(zeile)).length, 0)
|
||||||
})
|
})
|
||||||
|
|
||||||
// ---------------------------------------------------------------- Einbindung
|
// ---------------------------------------------------------------- Einbindung
|
||||||
|
|||||||
Reference in new issue
Block a user