Terminplaner: Zellen-Sweep seitenweise ueber idx_bis mit Deckel
raeumeVerwaisteZellen(now, { deckel = config.termine.zellenDeckel }) liest
Zellen mit bis < jetzt - 1 Tag in Seiten zu 100 ueber idx_bis (aelteste
zuerst, ohne Cursor neu gelesen), gruppiert je Ressourcensperre mit Map und
loescht unter der Sperre nur, was dort noch dieselbe Buchung hat und noch alt
ist. Die Buchungen werden nicht gelesen. Der Lauf endet am Deckel (5000,
gedeckelt wird im Tagesjob gezaehlt und im Log gewarnt), bei einer leeren
Seite oder einer Seite ohne Erfolg; der erste Fehler wird am Ende geworfen.
ungueltigeZellen prueft zuerst das Zeitfenster (auch fuer belegeZellen).
verwaisteZellen, alleZellen und ALLE_ZELLEN_MAX entfallen.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d040a55f89
commit
978722023f
6 files changed
+404
-158
No files matched your search
@@ -51,6 +51,7 @@ const ABSENDER_VORHER = config.smtp.from
|
||||
const MODUS_VORHER = config.termine.mode
|
||||
const DECKEL_VORHER = config.termine.loeschDeckel
|
||||
const GESAMT_DECKEL_VORHER = config.termine.loeschDeckelGesamt
|
||||
const ZELLEN_DECKEL_VORHER = config.termine.zellenDeckel
|
||||
const BASIS_VORHER = config.publicBaseUrl
|
||||
|
||||
const iso = (wert) => new Date(wert).toISOString()
|
||||
@@ -134,6 +135,7 @@ afterEach(async () => {
|
||||
config.termine.mode = MODUS_VORHER
|
||||
config.termine.loeschDeckel = DECKEL_VORHER
|
||||
config.termine.loeschDeckelGesamt = GESAMT_DECKEL_VORHER
|
||||
config.termine.zellenDeckel = ZELLEN_DECKEL_VORHER
|
||||
config.smtp.from = ABSENDER_VORHER
|
||||
config.publicBaseUrl = BASIS_VORHER
|
||||
assert.doesNotMatch(text, /Mustermann|max@example\.com/i, 'kein Gastname und keine Gastadresse im Log')
|
||||
@@ -928,9 +930,9 @@ test('wendeLoeschfristenAn: der Deckel gilt je Status; genau so viele fällige w
|
||||
test('wendeLoeschfristenAn: scheitert jedes Löschen, endet der Lauf nach einer Seite je Schritt, zählt die Fehler, lässt die anderen Schritte laufen; der nächste Lauf holt alles nach', async () => {
|
||||
for (const art of ARTEN) for (let i = 0; i < 30; i += 1) await legeLoeschFall(`b_${KUERZEL[art]}${nr(i)}`, art, 1 + i * 1000)
|
||||
await legeLoeschFall('b_ver_jung', 'verfallen', -1)
|
||||
// andere Schritte des Tagesjobs: eine alte, versandte Mail und eine verwaiste Zelle
|
||||
// andere Schritte des Tagesjobs: eine alte, versandte Mail und eine verwaiste Zelle (Buchung fehlt, Ende mehr als einen Tag vorbei: nur solche findet der Zellen-Sweep)
|
||||
legeEintrag('o_gesendet', { status: 'gesendet', an: '', html: '', text: '', naechsterVersuch: iso(plus(JETZT, -MIN)) })
|
||||
await belegeZellen({ featureId: F1, ressourceId: 'r_geist', bookingId: 'b_geist', start: plus(JETZT, 2 * TAG), blockEnde: plus(JETZT, 2 * TAG + 30 * MIN), now: JETZT })
|
||||
await belegeZellen({ featureId: F1, ressourceId: 'r_geist', bookingId: 'b_geist', start: plus(JETZT, -3 * TAG), blockEnde: plus(JETZT, -3 * TAG + 30 * MIN), now: JETZT })
|
||||
|
||||
let erg
|
||||
const zaehler = await mitScheiternderLoeschung(
|
||||
@@ -1629,8 +1631,8 @@ test('taeglicherJob: Löschfristen, Warteschlange, festgefahrene Mails, verwaist
|
||||
await legeBuchung('b_erledigt', { status: 'erledigt', ende: plus(JETZT, -100 * TAG) })
|
||||
await legeBuchung('b_zukunft', { start: plus(JETZT, 5 * TAG) })
|
||||
await legeBuchung('b_abgeschaltet', { featureId: F2, start: plus(JETZT, 5 * TAG) })
|
||||
// verwaiste Zelle: Buchung fehlt
|
||||
await belegeZellen({ featureId: F1, ressourceId: 'r_geist', bookingId: 'b_geist', start: plus(JETZT, 2 * TAG), blockEnde: plus(JETZT, 2 * TAG + 30 * MIN), now: JETZT })
|
||||
// verwaiste Zelle: Buchung fehlt, Ende mehr als einen Tag vorbei (nur solche findet der Zellen-Sweep)
|
||||
await belegeZellen({ featureId: F1, ressourceId: 'r_geist', bookingId: 'b_geist', start: plus(JETZT, -3 * TAG), blockEnde: plus(JETZT, -3 * TAG + 30 * MIN), now: JETZT })
|
||||
legeEintrag('o_gesendet', { status: 'gesendet', an: '', html: '', text: '', naechsterVersuch: iso(plus(JETZT, -MIN)) })
|
||||
legeEintrag('o_festgefahren', { createdAt: iso(plus(JETZT, -3 * TAG)) })
|
||||
|
||||
@@ -1688,6 +1690,69 @@ test('taeglicherJob: abgeschaltete Funktion am Deckel ⇒ gedeckelt.abgeschaltet
|
||||
assert.equal(aw.docs('termineConfig').get(F1), undefined)
|
||||
})
|
||||
|
||||
/** Alte, verwaiste Zellen (Buchung fehlt, Ende mehr als einen Tag vorbei), n Stück, die erste ist die älteste. */
|
||||
function legeAlteZellen(n, praefix = 'c_alt') {
|
||||
for (let i = 0; i < n; i += 1) {
|
||||
const bis = plus(JETZT, -3 * TAG - (n - i) * 1000)
|
||||
aw.seed('termineClaims', {
|
||||
$id: `${praefix}${String(i).padStart(3, '0')}`,
|
||||
featureId: F1,
|
||||
ressourceId: `r_geist${i % 3}`,
|
||||
bookingId: `b_geist${i}`,
|
||||
zelle: iso(plus(bis, -5 * MIN)),
|
||||
bis: iso(bis),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
test('taeglicherJob: Zellen-Sweep am Deckel ⇒ gedeckelt.zellen zählt, zellen bleibt die Zahl der gelöschten Zellen, der Rest folgt an den nächsten Tagen', async () => {
|
||||
legeAlteZellen(600)
|
||||
config.termine.zellenDeckel = 250
|
||||
|
||||
let erg = await taeglicherJob(JETZT)
|
||||
assert.equal(erg.zellen, 250)
|
||||
assert.deepEqual(erg.gedeckelt, { loeschfristen: 0, abgeschaltet: 0, zellen: 1 })
|
||||
assert.equal(erg.fehler, 0)
|
||||
assert.equal(aw.docs('termineClaims').size, 350)
|
||||
assert.equal(warnLog().filter((zeile) => /Deckel/.test(zeile)).length, 1)
|
||||
assert.doesNotMatch(warnLog().join('\n'), /\bb_|\bf_|\br_|\bc_/, 'die Warnung nennt keine Kennungen')
|
||||
const zeile = tagesbericht(erg)
|
||||
assert.match(zeile, /, 250 Zellen, /)
|
||||
assert.match(zeile, /Löschläufe am Deckel: 1, Rest folgt an den nächsten Tagen, 0 Fehler$/)
|
||||
|
||||
erg = await taeglicherJob(JETZT)
|
||||
assert.equal(erg.zellen, 250)
|
||||
assert.deepEqual(erg.gedeckelt, { loeschfristen: 0, abgeschaltet: 0, zellen: 1 })
|
||||
assert.equal(aw.docs('termineClaims').size, 100)
|
||||
|
||||
log.warn.mock.resetCalls()
|
||||
erg = await taeglicherJob(JETZT)
|
||||
assert.equal(erg.zellen, 100)
|
||||
assert.deepEqual(erg.gedeckelt, { loeschfristen: 0, abgeschaltet: 0, zellen: 0 })
|
||||
assert.equal(aw.docs('termineClaims').size, 0)
|
||||
assert.equal(warnLog().filter((zeile) => /Deckel/.test(zeile)).length, 0)
|
||||
assert.doesNotMatch(tagesbericht(erg), /Deckel/)
|
||||
})
|
||||
|
||||
test('taeglicherJob: Zellen-Sweep mit Standard-Deckel ⇒ zellen ist die Zahl, gedeckelt.zellen 0; nicht lesbare Zellen zählen als Fehler, die Zahlen bleiben Zahlen', async () => {
|
||||
legeAlteZellen(130)
|
||||
let erg = await taeglicherJob(JETZT)
|
||||
assert.equal(erg.zellen, 130)
|
||||
assert.deepEqual(erg.gedeckelt, { loeschfristen: 0, abgeschaltet: 0, zellen: 0 })
|
||||
assert.equal(erg.fehler, 0)
|
||||
|
||||
legeAlteZellen(5)
|
||||
aw.failNext({ collection: 'termineClaims', method: 'GET', status: 500, message: 'Nachbau kaputt' }) // die erste Seite des Sweeps
|
||||
erg = await taeglicherJob(JETZT)
|
||||
assert.equal(erg.zellen, 0)
|
||||
assert.deepEqual(erg.gedeckelt, { loeschfristen: 0, abgeschaltet: 0, zellen: 0 })
|
||||
assert.equal(erg.fehler, 1)
|
||||
assert.equal(aw.docs('termineClaims').size, 5)
|
||||
erg = await taeglicherJob(JETZT)
|
||||
assert.equal(erg.zellen, 5)
|
||||
assert.equal(erg.fehler, 0)
|
||||
})
|
||||
|
||||
test('taeglicherJob: ist die Liste der abgeschalteten Funktionen nicht lesbar, zählt der Schritt nichts, gedeckelt.abgeschaltet bleibt eine Zahl und die übrigen Schritte laufen', async () => {
|
||||
await abgeschaltetMitBuchungen(3, { zellen: false })
|
||||
aw.failNext({ collection: 'portalFeatures', method: 'GET', status: 500, message: 'Nachbau kaputt', after: 1 }) // 1. GET = Funktionen der Löschfristen, 2. = abgeschaltete
|
||||
|
||||
Reference in new issue
Block a user