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
+229
-92
@@ -19,7 +19,7 @@ useTestEnv({ appwriteUrl: aw.url, stripeUrl: 'http://127.0.0.1:1' })
|
||||
const { config } = await import('../server/config.js')
|
||||
const { deleteDocument, listDocuments } = await import('../server/services/appwriteAdmin.js')
|
||||
const { withLocks } = await import('../server/services/keyedLock.js')
|
||||
const { zellenFuer, zellenId, belegeZellen, gibZellenFrei, verwaisteZellen, raeumeVerwaisteZellen } = await import('../server/services/termine/zellen.js')
|
||||
const { zellenFuer, zellenId, belegeZellen, gibZellenFrei, raeumeVerwaisteZellen } = await import('../server/services/termine/zellen.js')
|
||||
const { neuesToken, hashToken, verwaltungsToken, buchungsNummer, formToken, pruefeFormToken } = await import(
|
||||
'../server/services/termine/token.js'
|
||||
)
|
||||
@@ -597,62 +597,7 @@ test('gibZellenFrei sperrt jede Ressource der Buchung, auch mehrere zugleich', a
|
||||
assert.equal(claimsOf('b_X').length, 0)
|
||||
})
|
||||
|
||||
test('verwaisteZellen: Buchung fehlt, nicht mehr belegend oder Zelle laenger als einen Tag vorbei', async () => {
|
||||
const claim = (id, bookingId, bis, extra = {}) =>
|
||||
aw.seed('termineClaims', {
|
||||
$id: id,
|
||||
featureId: F,
|
||||
ressourceId: R,
|
||||
bookingId,
|
||||
zelle: iso(new Date(new Date(bis).getTime() - 5 * MIN)),
|
||||
bis: iso(bis),
|
||||
...extra,
|
||||
})
|
||||
const bald = new Date(JETZT.getTime() + 2 * TAG)
|
||||
seedBuchung('b_ok', { status: 'bestaetigt' })
|
||||
seedBuchung('b_anfrage', { status: 'angefragt' })
|
||||
seedBuchung('b_haltung', { status: 'gehalten', heldUntil: iso(new Date(JETZT.getTime() + MIN)) })
|
||||
seedBuchung('b_haltung_alt', { status: 'gehalten', heldUntil: iso(new Date(JETZT.getTime() - MIN)) })
|
||||
seedBuchung('b_abgesagt', { status: 'abgesagt' })
|
||||
seedBuchung('b_erledigt', { status: 'erledigt' })
|
||||
seedBuchung('b_alt', { status: 'bestaetigt' })
|
||||
claim('c_ok', 'b_ok', bald)
|
||||
claim('c_anfrage', 'b_anfrage', bald)
|
||||
claim('c_haltung', 'b_haltung', bald)
|
||||
claim('c_haltung_alt', 'b_haltung_alt', bald)
|
||||
claim('c_abgesagt', 'b_abgesagt', bald)
|
||||
claim('c_erledigt', 'b_erledigt', bald)
|
||||
claim('c_fehlt', 'b_gibt_es_nicht', bald)
|
||||
claim('c_ohne', '', bald)
|
||||
claim('c_alt', 'b_alt', new Date(JETZT.getTime() - TAG - MIN)) // Buchung laeuft noch, die Zelle ist aber ueber einen Tag vorbei
|
||||
claim('c_gestern', 'b_ok', new Date(JETZT.getTime() - TAG + MIN)) // noch keinen vollen Tag vorbei, Buchung belegt
|
||||
const ids = await verwaisteZellen(JETZT)
|
||||
assert.deepEqual(
|
||||
[...ids].sort(),
|
||||
['c_abgesagt', 'c_alt', 'c_erledigt', 'c_fehlt', 'c_haltung_alt', 'c_ohne'].sort()
|
||||
)
|
||||
})
|
||||
|
||||
test('verwaisteZellen liest alle Zellen (mehr als eine Seite) und prueft ihre Buchungen in Bloecken', async () => {
|
||||
// 260 Zellen, je 2 pro Buchung (130 Buchungen, mehr als ein Block); jede zweite Buchung existiert nicht
|
||||
for (let i = 0; i < 260; i += 1) {
|
||||
const bookingId = `b_viele${Math.floor(i / 2)}`
|
||||
if (i % 4 < 2) seedBuchung(bookingId)
|
||||
aw.seed('termineClaims', {
|
||||
$id: `c_viele${String(i).padStart(3, '0')}`,
|
||||
featureId: F,
|
||||
ressourceId: R,
|
||||
bookingId,
|
||||
zelle: iso(new Date(T('10:00').getTime() + i * 5 * MIN)),
|
||||
bis: iso(T('23:00')),
|
||||
})
|
||||
}
|
||||
const ids = await verwaisteZellen(JETZT)
|
||||
const erwartet = []
|
||||
for (let i = 0; i < 260; i += 1) if (i % 4 >= 2) erwartet.push(`c_viele${String(i).padStart(3, '0')}`)
|
||||
assert.equal(erwartet.length, 130)
|
||||
assert.deepEqual([...ids].sort(), erwartet.sort())
|
||||
})
|
||||
// ---------------------------------------------------------------- Zellen-Sweep (raeumeVerwaisteZellen) und Zeitfenster
|
||||
|
||||
/** Zellen mit eigener ID in der Datenbank anlegen (ohne belegeZellen, damit auch ungueltige Altlasten entstehen). */
|
||||
function seedZelle(id, bookingId, bis, extra = {}) {
|
||||
@@ -667,79 +612,271 @@ function seedZelle(id, bookingId, bis, extra = {}) {
|
||||
})
|
||||
}
|
||||
|
||||
test('raeumeVerwaisteZellen loescht genau die verwaisten Zellen und zaehlt sie', async () => {
|
||||
/** Ende einer alten Zelle: mehr als einen Tag vor JETZT (n ms weiter zurueck, damit die Sortierung nach bis eindeutig ist). */
|
||||
const alt = (n = 0) => new Date(JETZT.getTime() - TAG - 1 - n)
|
||||
const nr = (i, stellen = 3) => String(i).padStart(stellen, '0')
|
||||
|
||||
/** Alle Anfragen (Methode und Adresse) aufzeichnen, die fn an Appwrite stellt. */
|
||||
async function mitAnfragen(fn) {
|
||||
const echt = globalThis.fetch
|
||||
const anfragen = []
|
||||
globalThis.fetch = async (url, optionen) => {
|
||||
anfragen.push({ methode: optionen?.method ?? 'GET', url: String(url) })
|
||||
return echt(url, optionen)
|
||||
}
|
||||
try {
|
||||
await fn()
|
||||
} finally {
|
||||
globalThis.fetch = echt
|
||||
}
|
||||
return anfragen
|
||||
}
|
||||
const anBuchungen = (anfragen) => anfragen.filter((anfrage) => anfrage.url.includes('/collections/termineBookings/'))
|
||||
|
||||
/** DELETE-Anfragen an termineClaims scheitern, solange scheitert(id) wahr ist (503); alles andere geht durch. */
|
||||
async function mitScheiternderZellenLoeschung(scheitert, fn) {
|
||||
const echt = globalThis.fetch
|
||||
globalThis.fetch = async (url, optionen) => {
|
||||
const treffer = optionen?.method === 'DELETE' && String(url).match(/\/collections\/termineClaims\/documents\/([^/?]+)$/)
|
||||
if (treffer && scheitert(treffer[1])) {
|
||||
return new Response(JSON.stringify({ message: 'Nachbau kaputt', code: 503, type: 'general_unknown' }), {
|
||||
status: 503,
|
||||
headers: { 'content-type': 'application/json' },
|
||||
})
|
||||
}
|
||||
return echt(url, optionen)
|
||||
}
|
||||
try {
|
||||
return await fn()
|
||||
} finally {
|
||||
globalThis.fetch = echt
|
||||
}
|
||||
}
|
||||
|
||||
/** Abfragen des Sweeps (die Seiten ueber bis), nicht die ID-Abfragen unter der Sperre. */
|
||||
const seitenAbfragen = () => aw.listCalls('termineClaims').filter((aufruf) => aufruf.queries.some((q) => q.method === 'lessThan'))
|
||||
const limitVon = (aufruf) => aufruf.queries.find((q) => q.method === 'limit')?.values[0]
|
||||
|
||||
test('Zellen-Sweep: bis genau jetzt - 1 Tag bleibt, eine Millisekunde aelter geht, die Buchungen werden nie abgefragt', async () => {
|
||||
const grenze = new Date(JETZT.getTime() - TAG)
|
||||
seedBuchung('b_ok') // bestaetigt: die Buchung belegt noch
|
||||
seedZelle('c_genau', 'b_ok', grenze)
|
||||
seedZelle('c_aelter', 'b_ok', new Date(grenze.getTime() - 1)) // ueber einen Tag vorbei: ungueltig, egal was die Buchung sagt
|
||||
seedZelle('c_fehlt_genau', 'b_gibt_es_nicht', grenze) // Buchung fehlt, aber das Zeitfenster zaehlt: bleibt
|
||||
seedZelle('c_fehlt_kuenftig', 'b_gibt_es_nicht', new Date(JETZT.getTime() + 2 * TAG))
|
||||
let erg
|
||||
const anfragen = await mitAnfragen(async () => {
|
||||
erg = await raeumeVerwaisteZellen(JETZT)
|
||||
})
|
||||
assert.deepEqual(erg, { geloescht: 1, gedeckelt: false })
|
||||
assert.deepEqual([...aw.docs('termineClaims').keys()].sort(), ['c_fehlt_genau', 'c_fehlt_kuenftig', 'c_genau'])
|
||||
assert.deepEqual(anBuchungen(anfragen), [], 'keine einzige Anfrage an termineBookings')
|
||||
|
||||
// eine Millisekunde spaeter sind auch die beiden Zellen mit bis == jetzt - 1 Tag ueber einen Tag vorbei
|
||||
assert.deepEqual(await raeumeVerwaisteZellen(new Date(JETZT.getTime() + 1)), { geloescht: 2, gedeckelt: false })
|
||||
assert.deepEqual([...aw.docs('termineClaims').keys()], ['c_fehlt_kuenftig'])
|
||||
await assert.rejects(raeumeVerwaisteZellen(undefined), TypeError)
|
||||
})
|
||||
|
||||
test('Zellen-Sweep loescht genau die alten Zellen (jeder Funktion und Ressource) und zaehlt sie; kuenftige bleiben, auch ohne gueltige Buchung', async () => {
|
||||
const bald = new Date(JETZT.getTime() + 2 * TAG)
|
||||
seedBuchung('b_ok')
|
||||
seedBuchung('b_abgesagt', { status: 'abgesagt' })
|
||||
seedBuchung('b_haltung_alt', { status: 'gehalten', heldUntil: iso(new Date(JETZT.getTime() - MIN)) })
|
||||
seedBuchung('b_alt')
|
||||
// kuenftiges Ende: bleibt, auch wenn die Buchung fehlt oder nichts belegt (siehe die Waisen-Regel im Kopfkommentar)
|
||||
seedZelle('c_ok', 'b_ok', bald)
|
||||
seedZelle('c_abgesagt', 'b_abgesagt', bald, { ressourceId: 'r_aaaaaaa2' })
|
||||
seedZelle('c_haltung_alt', 'b_haltung_alt', bald)
|
||||
seedZelle('c_fehlt', 'b_gibt_es_nicht', bald, { featureId: 'f_feature02' })
|
||||
seedZelle('c_alt', 'b_alt', new Date(JETZT.getTime() - TAG - MIN))
|
||||
assert.equal(await raeumeVerwaisteZellen(JETZT), 4)
|
||||
assert.deepEqual([...aw.docs('termineClaims').keys()], ['c_ok'])
|
||||
assert.equal(await raeumeVerwaisteZellen(JETZT), 0)
|
||||
await assert.rejects(raeumeVerwaisteZellen(undefined), TypeError)
|
||||
// Ende mehr als einen Tag vorbei: weg, egal was die Buchung sagt
|
||||
seedZelle('c_alt_ok', 'b_ok', alt(1000))
|
||||
seedZelle('c_alt_abgesagt', 'b_abgesagt', alt(2000), { ressourceId: 'r_aaaaaaa2' })
|
||||
seedZelle('c_alt_haltung', 'b_haltung_alt', alt(3000))
|
||||
seedZelle('c_alt_fehlt', 'b_gibt_es_nicht', alt(4000), { featureId: 'f_feature02' })
|
||||
assert.deepEqual(await raeumeVerwaisteZellen(JETZT), { geloescht: 4, gedeckelt: false })
|
||||
assert.deepEqual([...aw.docs('termineClaims').keys()].sort(), ['c_abgesagt', 'c_fehlt', 'c_ok'])
|
||||
assert.deepEqual(await raeumeVerwaisteZellen(JETZT), { geloescht: 0, gedeckelt: false })
|
||||
})
|
||||
|
||||
test('raeumeVerwaisteZellen raeumt mehr als einen Block (260 Zellen auf zwei Ressourcen)', async () => {
|
||||
test('Zellen-Sweep: 260 alte Zellen auf zwei Ressourcen in Seiten zu hoechstens 100 ueber bis (Standard-Deckel liegt darueber)', async () => {
|
||||
assert.equal(config.termine.zellenDeckel, 5000)
|
||||
for (let i = 0; i < 260; i += 1) {
|
||||
seedZelle(`c_viele${String(i).padStart(3, '0')}`, `b_nie${i}`, T('23:00'), { ressourceId: i % 2 ? 'r_aaaaaaa2' : R })
|
||||
seedZelle(`c_viele${nr(i)}`, `b_nie${i}`, alt(i * 1000), { ressourceId: i % 2 ? 'r_aaaaaaa2' : R })
|
||||
}
|
||||
seedBuchung('b_ok')
|
||||
seedZelle('c_ok', 'b_ok', T('23:00'))
|
||||
assert.equal(await raeumeVerwaisteZellen(JETZT), 260)
|
||||
aw.resetListCalls()
|
||||
assert.deepEqual(await raeumeVerwaisteZellen(JETZT), { geloescht: 260, gedeckelt: false })
|
||||
assert.deepEqual([...aw.docs('termineClaims').keys()], ['c_ok'])
|
||||
|
||||
const abfragen = aw.listCalls('termineClaims')
|
||||
assert.ok(abfragen.length >= 4, `drei Seiten, eine leere Seite und die ID-Abfragen: ${abfragen.length}`)
|
||||
assert.ok(abfragen.every((aufruf) => Number.isInteger(limitVon(aufruf)) && limitVon(aufruf) <= 100), abfragen.map(limitVon).join(','))
|
||||
const seiten = seitenAbfragen()
|
||||
assert.equal(seiten.length, 4, '100 + 100 + 60 Zellen, dann eine leere Seite')
|
||||
const erste = seiten[0].queries
|
||||
assert.deepEqual(erste.find((q) => q.method === 'lessThan'), { method: 'lessThan', attribute: 'bis', values: [iso(new Date(JETZT.getTime() - TAG))] })
|
||||
assert.equal(erste.find((q) => q.method === 'orderAsc')?.attribute, 'bis')
|
||||
assert.deepEqual(erste.find((q) => q.method === 'select')?.values, ['$id', 'featureId', 'ressourceId', 'bookingId', 'bis'])
|
||||
assert.equal(erste.find((q) => q.method === 'cursorAfter'), undefined, 'ohne Cursor, die Liste wird neu gelesen')
|
||||
assert.deepEqual(seiten.map(limitVon), [100, 100, 100, 100])
|
||||
})
|
||||
|
||||
test('Zellen-Sweep: der Deckel zaehlt Zellen genau, der Rest folgt im naechsten Lauf', async () => {
|
||||
// c_deckel000 ist die aelteste
|
||||
for (let i = 0; i < 600; i += 1) seedZelle(`c_deckel${nr(i)}`, `b_nie${i}`, alt((600 - i) * 1000), { ressourceId: i % 3 ? 'r_aaaaaaa2' : R })
|
||||
const vorher = config.termine.zellenDeckel
|
||||
config.termine.zellenDeckel = 250
|
||||
try {
|
||||
assert.deepEqual(await raeumeVerwaisteZellen(JETZT), { geloescht: 250, gedeckelt: true })
|
||||
assert.equal(aw.docs('termineClaims').size, 350)
|
||||
assert.ok(!aw.docs('termineClaims').has('c_deckel249') && aw.docs('termineClaims').has('c_deckel250'), 'die aeltesten zuerst')
|
||||
assert.deepEqual(await raeumeVerwaisteZellen(JETZT), { geloescht: 250, gedeckelt: true })
|
||||
assert.equal(aw.docs('termineClaims').size, 100)
|
||||
assert.deepEqual(await raeumeVerwaisteZellen(JETZT), { geloescht: 100, gedeckelt: false })
|
||||
assert.equal(alleClaims(), 0)
|
||||
assert.deepEqual(await raeumeVerwaisteZellen(JETZT), { geloescht: 0, gedeckelt: false })
|
||||
} finally {
|
||||
config.termine.zellenDeckel = vorher
|
||||
}
|
||||
})
|
||||
|
||||
test('Zellen-Sweep: deckel als Option gilt vor config.termine.zellenDeckel; genau so viele Zellen wie der Deckel sind nicht gedeckelt', async () => {
|
||||
for (let i = 0; i < 10; i += 1) seedZelle(`c_opt${nr(i)}`, `b_nie${i}`, alt((10 - i) * 1000))
|
||||
assert.deepEqual(await raeumeVerwaisteZellen(JETZT, { deckel: 4 }), { geloescht: 4, gedeckelt: true })
|
||||
assert.deepEqual(await raeumeVerwaisteZellen(JETZT, { deckel: 0 }), { geloescht: 0, gedeckelt: true }, 'Deckel 0: nur nachsehen, ob etwas ansteht')
|
||||
assert.equal(alleClaims(), 6)
|
||||
assert.deepEqual(await raeumeVerwaisteZellen(JETZT, { deckel: 6 }), { geloescht: 6, gedeckelt: false }, 'der Deckel reicht genau')
|
||||
assert.deepEqual(await raeumeVerwaisteZellen(JETZT, { deckel: 0 }), { geloescht: 0, gedeckelt: false })
|
||||
for (const falsch of [-1, 1.5, '5', null, NaN]) {
|
||||
await assert.rejects(raeumeVerwaisteZellen(JETZT, { deckel: falsch }), TypeError, String(falsch))
|
||||
}
|
||||
})
|
||||
|
||||
test('Zellen-Sweep: 3000 alte Zellen einer einzigen Ressource (ein Sperrschluessel) werden alle geloescht', async () => {
|
||||
for (let i = 0; i < 3000; i += 1) seedZelle(`c_eine${nr(i, 4)}`, `b_nie${i % 500}`, alt((3000 - i) * 1000))
|
||||
seedBuchung('b_ok')
|
||||
seedZelle('c_ok', 'b_ok', T('23:00'))
|
||||
assert.deepEqual(await raeumeVerwaisteZellen(JETZT), { geloescht: 3000, gedeckelt: false })
|
||||
assert.deepEqual([...aw.docs('termineClaims').keys()], ['c_ok'])
|
||||
})
|
||||
|
||||
test('raeumeVerwaisteZellen: ein verzoegertes DELETE trifft nicht die frisch belegten Zellen einer neuen Buchung', async () => {
|
||||
test('Zellen-Sweep: ein verzoegertes DELETE trifft nicht die Zellen einer gleichzeitig belegten Zeit derselben Ressource', async () => {
|
||||
seedBuchung('b_X', { status: 'abgesagt' })
|
||||
await belegeZellen({ featureId: F, ressourceId: R, bookingId: 'b_X', start: T('10:00'), blockEnde: T('10:50'), now: JETZT })
|
||||
for (let i = 0; i < 10; i += 1) seedZelle(`c_x${nr(i, 2)}`, 'b_X', alt(i * 1000))
|
||||
seedBuchung('b_W')
|
||||
await mitVerzoegertemLoeschen(10, 400, async () => {
|
||||
const lauf = raeumeVerwaisteZellen(JETZT)
|
||||
await schlafe(20)
|
||||
await belegeZellen({ featureId: F, ressourceId: R, bookingId: 'b_W', start: T('10:00'), blockEnde: T('10:50'), now: JETZT })
|
||||
await lauf
|
||||
assert.deepEqual(await lauf, { geloescht: 10, gedeckelt: false })
|
||||
})
|
||||
assert.equal(claimsOf('b_W').length, 10, 'W behaelt alle Zellen')
|
||||
assert.equal(claimsOf('b_X').length, 0)
|
||||
})
|
||||
|
||||
test('raeumeVerwaisteZellen prueft nach dem Warten auf die Sperre erneut: wieder gueltige oder neu vergebene Zellen bleiben', async () => {
|
||||
test('Zellen-Sweep prueft nach dem Warten auf die Sperre erneut: nur dieselbe Buchung und noch alt wird geloescht', async () => {
|
||||
const bald = new Date(JETZT.getTime() + 2 * TAG)
|
||||
seedBuchung('b_X1', { status: 'abgesagt' })
|
||||
seedBuchung('b_X2', { status: 'abgesagt' })
|
||||
seedBuchung('b_X3', { status: 'abgesagt' })
|
||||
seedBuchung('b_W')
|
||||
seedZelle('c_x1', 'b_X1', bald)
|
||||
seedZelle('c_x2', 'b_X2', bald)
|
||||
seedZelle('c_x3', 'b_X3', bald)
|
||||
seedZelle('c_x4', 'b_X1', bald)
|
||||
seedZelle('c_x1', 'b_X1', alt(1000)) // unveraendert: weg
|
||||
seedZelle('c_x2', 'b_X2', alt(2000)) // wird einer neuen Buchung mit kuenftigem Ende
|
||||
seedZelle('c_x3', 'b_X3', alt(3000)) // ist inzwischen schon weg
|
||||
seedZelle('c_x4', 'b_X1', alt(4000)) // andere Buchung, deren Dokument erst noch folgt
|
||||
seedZelle('c_x5', 'b_X5', alt(5000)) // gleiche Buchung, aber das Ende liegt jetzt in der Zukunft
|
||||
seedZelle('c_x6', 'b_X6', alt(6000)) // andere Buchung, das Ende ist noch alt: bleibt bis zum naechsten Lauf
|
||||
const sperre = haltePerSperre()
|
||||
aw.resetListCalls()
|
||||
const lauf = raeumeVerwaisteZellen(JETZT)
|
||||
await schlafe(100) // der Lauf hat die Kandidaten gelesen und wartet auf die Sperre
|
||||
seedBuchung('b_X1', { status: 'bestaetigt' }) // wieder gueltig
|
||||
aw.seed('termineClaims', { ...aw.docs('termineClaims').get('c_x2'), bookingId: 'b_W' }) // neu vergeben
|
||||
await deleteDocument('termineClaims', 'c_x3') // schon weg
|
||||
aw.seed('termineClaims', { ...aw.docs('termineClaims').get('c_x4'), bookingId: 'b_neu_ohne_buchung' }) // neu vergeben, Buchung folgt erst noch
|
||||
await schlafe(100) // der Lauf hat die Seite gelesen und wartet auf die Sperre
|
||||
const zelle = (id) => aw.docs('termineClaims').get(id)
|
||||
aw.seed('termineClaims', { ...zelle('c_x2'), bookingId: 'b_W', bis: iso(bald) })
|
||||
await deleteDocument('termineClaims', 'c_x3')
|
||||
aw.seed('termineClaims', { ...zelle('c_x4'), bookingId: 'b_neu_ohne_buchung', bis: iso(bald) })
|
||||
aw.seed('termineClaims', { ...zelle('c_x5'), bis: iso(bald) })
|
||||
aw.seed('termineClaims', { ...zelle('c_x6'), bookingId: 'b_W' })
|
||||
sperre.freigeben()
|
||||
await sperre.fertig
|
||||
assert.equal(await lauf, 0)
|
||||
assert.deepEqual([...aw.docs('termineClaims').keys()].sort(), ['c_x1', 'c_x2', 'c_x4'])
|
||||
assert.equal(aw.docs('termineClaims').get('c_x2').bookingId, 'b_W')
|
||||
assert.equal(aw.docs('termineClaims').get('c_x4').bookingId, 'b_neu_ohne_buchung')
|
||||
assert.deepEqual(await lauf, { geloescht: 1, gedeckelt: false })
|
||||
assert.deepEqual([...aw.docs('termineClaims').keys()].sort(), ['c_x2', 'c_x4', 'c_x5', 'c_x6'])
|
||||
assert.equal(zelle('c_x2').bookingId, 'b_W')
|
||||
assert.equal(zelle('c_x4').bookingId, 'b_neu_ohne_buchung')
|
||||
assert.equal(seitenAbfragen().length <= 3, true, 'keine Endlosschleife ueber die unveraenderte c_x6')
|
||||
// im naechsten Lauf gehoert c_x6 der Buchung, die der Sweep liest: weg
|
||||
assert.deepEqual(await raeumeVerwaisteZellen(JETZT), { geloescht: 1, gedeckelt: false })
|
||||
assert.deepEqual([...aw.docs('termineClaims').keys()].sort(), ['c_x2', 'c_x4', 'c_x5'])
|
||||
})
|
||||
|
||||
test('raeumeVerwaisteZellen: ein Fehler in einem Block stoppt die uebrigen nicht und wird am Ende geworfen', async () => {
|
||||
seedZelle('c_a1', 'b_nie1', T('23:00'))
|
||||
seedZelle('c_b1', 'b_nie2', T('23:00'), { ressourceId: 'r_aaaaaaa2' })
|
||||
test('Zellen-Sweep: eine Waise mit kuenftigem Ende bleibt stehen, blockiert aber keine Neubuchung', async () => {
|
||||
seedBuchung('b_abgesagt', { status: 'abgesagt' })
|
||||
await belegeZellen({ featureId: F, ressourceId: R, bookingId: 'b_abgesagt', start: T('10:00'), blockEnde: T('10:50'), now: JETZT })
|
||||
seedZelle('c_fehlt', 'b_gibt_es_nicht', T('23:00'))
|
||||
assert.deepEqual(await raeumeVerwaisteZellen(JETZT), { geloescht: 0, gedeckelt: false })
|
||||
assert.equal(alleClaims(), 11, 'der Sweep findet Waisen mit kuenftigem Ende nicht mehr')
|
||||
|
||||
seedBuchung('b_neu')
|
||||
await belegeZellen({ featureId: F, ressourceId: R, bookingId: 'b_neu', start: T('10:00'), blockEnde: T('10:50'), now: JETZT })
|
||||
assert.equal(claimsOf('b_neu').length, 10, 'belegeZellen raeumt den Blockierer')
|
||||
assert.equal(claimsOf('b_abgesagt').length, 0)
|
||||
|
||||
// spaetestens einen Tag nach bis faellt alles in den Sweep
|
||||
assert.deepEqual(await raeumeVerwaisteZellen(new Date(T('23:00').getTime() + TAG + MIN)), { geloescht: 11, gedeckelt: false })
|
||||
assert.equal(alleClaims(), 0)
|
||||
})
|
||||
|
||||
test('Zellen-Sweep: ein Fehler beim Loeschen stoppt die uebrigen Ressourcen nicht und wird am Ende geworfen', async () => {
|
||||
seedZelle('c_a1', 'b_nie1', alt(2000))
|
||||
seedZelle('c_b1', 'b_nie2', alt(1000), { ressourceId: 'r_aaaaaaa2' })
|
||||
aw.resetListCalls()
|
||||
aw.failNext({ collection: 'termineClaims', method: 'DELETE', status: 503 })
|
||||
await assert.rejects(raeumeVerwaisteZellen(JETZT), (err) => err.status === 500)
|
||||
assert.equal(aw.docs('termineClaims').size, 1, 'die andere Ressource ist trotzdem geraeumt')
|
||||
assert.equal(await raeumeVerwaisteZellen(JETZT), 1)
|
||||
assert.ok(seitenAbfragen().length <= 2, `kein Endlosdurchlauf: ${seitenAbfragen().length} Seiten`)
|
||||
assert.deepEqual(await raeumeVerwaisteZellen(JETZT), { geloescht: 1, gedeckelt: false })
|
||||
})
|
||||
|
||||
test('Zellen-Sweep: scheitert jedes DELETE, endet der Lauf nach einer Seite und wirft den ersten Fehler', async () => {
|
||||
for (let i = 0; i < 150; i += 1) seedZelle(`c_kaputt${nr(i)}`, `b_nie${i}`, alt((150 - i) * 1000), { ressourceId: i % 2 ? 'r_aaaaaaa2' : R })
|
||||
aw.resetListCalls()
|
||||
await mitScheiternderZellenLoeschung(
|
||||
() => true,
|
||||
() => assert.rejects(raeumeVerwaisteZellen(JETZT), (err) => err.status === 500)
|
||||
)
|
||||
assert.equal(alleClaims(), 150)
|
||||
assert.equal(seitenAbfragen().length, 1, 'eine Seite ohne Erfolg beendet den Lauf')
|
||||
})
|
||||
|
||||
test('Zellen-Sweep: eine Zelle, die sich nicht loeschen laesst, haelt den Rest nicht auf; was geloescht ist, zaehlt fuer den naechsten Lauf', async () => {
|
||||
// c_stur000 ist die aelteste und steht in jeder Liste vorn
|
||||
for (let i = 0; i < 150; i += 1) seedZelle(`c_stur${nr(i)}`, `b_nie${i}`, alt((150 - i) * 1000))
|
||||
aw.resetListCalls()
|
||||
await mitScheiternderZellenLoeschung(
|
||||
(id) => id === 'c_stur000',
|
||||
() => assert.rejects(raeumeVerwaisteZellen(JETZT), (err) => err.status === 500)
|
||||
)
|
||||
assert.deepEqual([...aw.docs('termineClaims').keys()], ['c_stur000'])
|
||||
assert.ok(seitenAbfragen().length <= 4, `keine Endlosschleife: ${seitenAbfragen().length} Seiten`)
|
||||
assert.deepEqual(await raeumeVerwaisteZellen(JETZT), { geloescht: 1, gedeckelt: false })
|
||||
})
|
||||
|
||||
test('Blockierer mit Ende vor mehr als einem Tag sind ohne Buchungsabfrage ungueltig (dieselbe Regel wie im Sweep)', async () => {
|
||||
await belege('b_alt', T('10:00'), T('11:00')) // bestaetigt: belegt
|
||||
seedBuchung('b_neu')
|
||||
const spaet = new Date(T('11:00').getTime() + TAG + MIN)
|
||||
const anfragen = await mitAnfragen(() =>
|
||||
belegeZellen({ featureId: F, ressourceId: R, bookingId: 'b_neu', start: T('10:00'), blockEnde: T('11:00'), now: spaet })
|
||||
)
|
||||
assert.equal(claimsOf('b_neu').length, 12)
|
||||
assert.equal(claimsOf('b_alt').length, 0)
|
||||
assert.deepEqual(anBuchungen(anfragen), [], 'das Zeitfenster entscheidet, die Buchung wird nicht gelesen')
|
||||
|
||||
// genau einen Tag nach bis ist noch nichts ueberfaellig: die Buchung entscheidet und belegt
|
||||
await belege('b_alt2', T('12:00'), T('13:00'))
|
||||
seedBuchung('b_neu2')
|
||||
const knapp = new Date(T('13:00').getTime() + TAG)
|
||||
const anfragen2 = await mitAnfragen(() =>
|
||||
assert.rejects(belegeZellen({ featureId: F, ressourceId: R, bookingId: 'b_neu2', start: T('12:00'), blockEnde: T('13:00'), now: knapp }), vergeben)
|
||||
)
|
||||
assert.ok(anBuchungen(anfragen2).length > 0)
|
||||
assert.equal(claimsOf('b_alt2').length, 12)
|
||||
})
|
||||
|
||||
// ---------------------------------------------------------------- Buchungsspeicher
|
||||
|
||||
Reference in new issue
Block a user