Abo-Abgleich: Rückkehr innerhalb der Frist schaltet wieder ein (K23)
Schritt 3 von gleicheAboAb: Ein abgeschaltetes Dokument mit Grund abo_ende, dessen Wartungs-Abo wieder läuft, wird unter feature-admin:<projectId> nach frischem Lesen von Dokument und Abo und mit vorhandener Einstellung über activate wieder eingeschaltet (Daten und Einstellungen bleiben). K23 geht nur, wenn K22 ging (aboEndeMailAt vor dem activate gelesen); scheitert K23, bleibt die Reaktivierung bestehen. Nicht automatisch: off nach der Löschung, abo_ende_vor_einrichtung, team, kunde_geloescht, Altbestand ohne Grund. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
addb88d5fd
commit
13523ca5e2
2 files changed
+457
-12
No files matched your search
@@ -5,9 +5,9 @@ import { mailSink } from './support/mailSink.js'
|
||||
import { schemaKeys } from './support/schema.js'
|
||||
import { useTestEnv } from './support/testEnv.js'
|
||||
|
||||
// Abo-Abgleich des Terminplaners (server/services/termine/aboAbgleich.js, Terminplaner TP2 E4/E5): aktive Terminplaner ohne laufendes
|
||||
// Wartungs-Abo abschalten (Schritt 1), Einrichtungen, die das Abo-Ende erreicht, abschalten und erstatten (Schritt 1b, E5) und die
|
||||
// Mail K22 nachholen (Schritt 2). Aufbau wie termineJobs.test.js: Nachbau von Appwrite mit Schema und Standard-Limit 25, Mails in
|
||||
// Abo-Abgleich des Terminplaners (server/services/termine/aboAbgleich.js, Terminplaner TP2 E4/E5/E6): aktive Terminplaner ohne laufendes
|
||||
// Wartungs-Abo abschalten (Schritt 1), Einrichtungen, die das Abo-Ende erreicht, abschalten und erstatten (Schritt 1b, E5), die
|
||||
// Mail K22 nachholen (Schritt 2) und bei zurueckgekehrtem Abo wieder einschalten, mit K23 (Schritt 3, E6). Aufbau wie termineJobs.test.js: Nachbau von Appwrite mit Schema und Standard-Limit 25, Mails in
|
||||
// der Senke, feste Zeit JETZT. Stripe wird nie erreicht (Port 1).
|
||||
const aw = await startAppwriteMock({
|
||||
schema: {
|
||||
@@ -15,6 +15,7 @@ const aw = await startAppwriteMock({
|
||||
subscriptions: schemaKeys('subscriptions'),
|
||||
contractEvents: schemaKeys('contractEvents'),
|
||||
termineConfig: schemaKeys('termineConfig'),
|
||||
termineBookings: schemaKeys('termineBookings'),
|
||||
},
|
||||
defaultLimit: 25,
|
||||
})
|
||||
@@ -43,7 +44,7 @@ let sink
|
||||
let log
|
||||
|
||||
beforeEach(async () => {
|
||||
for (const collection of ['portalFeatures', 'subscriptions', 'contractEvents', 'termineConfig', 'customers', 'websiteProjects', 'workorders']) {
|
||||
for (const collection of ['portalFeatures', 'subscriptions', 'contractEvents', 'termineConfig', 'termineBookings', 'customers', 'websiteProjects', 'workorders']) {
|
||||
for (const id of aw.docs(collection).keys()) await deleteDocument(collection, id)
|
||||
}
|
||||
aw.resetListCalls()
|
||||
@@ -536,7 +537,7 @@ for (const [fall, vorbereitung] of [
|
||||
})
|
||||
}
|
||||
|
||||
test('K22-Nachlauf: das Abo laeuft wieder - Schritt 2 uebergeben (E6), keine Mail, Marker bleibt leer', async () => {
|
||||
test('K22-Nachlauf: das Abo laeuft wieder (ohne Einstellung) - kein K22, Marker bleibt leer, das Dokument bleibt abgeschaltet (Schritt 3 braucht die Einstellung)', async () => {
|
||||
legeFunktion(1, { state: 'disabled', enabled: false, disabledReason: 'abo_ende', disabledAt: plus(JETZT, -2 * TAG).toISOString() })
|
||||
wartung('active')
|
||||
assert.deepEqual(await laufe(), ergebnis({ geprueft: 1 }))
|
||||
@@ -933,3 +934,380 @@ test('meldeAboEnde direkt: bezahlt, aber Kunde nicht mehr vorhanden - T19 geht t
|
||||
assert.equal(funktion(id).zahlungMeldung, 'gemeldet')
|
||||
assert.ok(funktion(id).aboEndeMailAt, 'kein Wiederholen')
|
||||
})
|
||||
|
||||
// ---------------------------------------------------------------- Schritt 3: Rueckkehr des Abos, Wiedereinschalten und K23 (E6)
|
||||
|
||||
const K23_BETREFF = 'Dein Terminplaner läuft wieder – Praxis Muster'
|
||||
const k23 = () => mails().filter((m) => /^Dein Terminplaner läuft wieder/.test(m.subject))
|
||||
const bookingId = (nr) => `b_${funktionsId(nr)}`
|
||||
|
||||
/** Einstellung (termineConfig, Version 7) und eine kuenftige Buchung der Funktion nr: die Daten, die ein Wiedereinschalten behalten muss. */
|
||||
function legeDaten(nr = 1) {
|
||||
aw.seed('termineConfig', {
|
||||
$id: funktionsId(nr),
|
||||
customerId: KUNDE,
|
||||
projectId: projektId(nr),
|
||||
einstellung: JSON.stringify({ betrieb: { name: 'Salon Müller' }, anrede: 'sie' }),
|
||||
version: 7,
|
||||
updatedAt: plus(JETZT, -20 * TAG).toISOString(),
|
||||
updatedBy: 'silke@example.com',
|
||||
})
|
||||
aw.seed('termineBookings', {
|
||||
$id: bookingId(nr),
|
||||
featureId: funktionsId(nr),
|
||||
customerId: KUNDE,
|
||||
projectId: projektId(nr),
|
||||
status: 'bestaetigt',
|
||||
startsAt: plus(JETZT, 3 * TAG).toISOString(),
|
||||
endsAt: plus(JETZT, 3 * TAG + 45 * 60_000).toISOString(),
|
||||
gastName: 'Max Mustermann',
|
||||
})
|
||||
}
|
||||
const datenStand = (nr = 1) => ({
|
||||
einstellung: aw.docs('termineConfig').get(funktionsId(nr)) && { ...aw.docs('termineConfig').get(funktionsId(nr)) },
|
||||
buchung: aw.docs('termineBookings').get(bookingId(nr)) && { ...aw.docs('termineBookings').get(bookingId(nr)) },
|
||||
})
|
||||
|
||||
/** Vor 10 Tagen wegen des Abo-Endes abgeschalteter Terminplaner (K22 ging: Marker gesetzt) mit Einstellung und Buchung. */
|
||||
function legeAbgeschaltet(nr = 1, over = {}, { daten = true, ...kontext } = {}) {
|
||||
const id = legeFunktion(
|
||||
nr,
|
||||
{
|
||||
state: 'disabled',
|
||||
enabled: false,
|
||||
disabledReason: 'abo_ende',
|
||||
disabledAt: plus(JETZT, -10 * TAG).toISOString(),
|
||||
aboEndeMailAt: plus(JETZT, -10 * TAG).toISOString(),
|
||||
adminNote: 'Abgeschaltet: Wartungs-Abo beendet',
|
||||
...over,
|
||||
},
|
||||
kontext,
|
||||
)
|
||||
if (daten) legeDaten(nr)
|
||||
return id
|
||||
}
|
||||
|
||||
let wechselNr = 0
|
||||
/** Vertragsereignis "wechsel" des Wartungs-Abos sub_w; $createdAt steigt mit jedem Aufruf ("das juengste" ist eindeutig). */
|
||||
function wechselEreignis(status) {
|
||||
wechselNr += 1
|
||||
aw.seed('contractEvents', {
|
||||
$id: `ev_w${wechselNr}`,
|
||||
$createdAt: new Date(Date.UTC(2026, 8, 1, 0, 0, wechselNr)).toISOString(),
|
||||
customerId: KUNDE,
|
||||
subscriptionId: 'sub_w',
|
||||
type: 'wechsel',
|
||||
productKey: 'wartung',
|
||||
details: '{}',
|
||||
status,
|
||||
})
|
||||
}
|
||||
|
||||
test('Rueckkehr innerhalb von 30 Tagen: wieder aktiv, Einstellung und Buchung unveraendert, K23 genau einmal, am Folgetag nichts', async () => {
|
||||
legeAbgeschaltet(1)
|
||||
wartung('active')
|
||||
const daten = datenStand()
|
||||
assert.ok(daten.einstellung && daten.buchung)
|
||||
|
||||
assert.deepEqual(await laufe(), ergebnis({ geprueft: 1, wiederEin: 1, mails: 1 }))
|
||||
const doc = funktion()
|
||||
assert.equal(doc.state, 'active')
|
||||
assert.equal(doc.enabled, true)
|
||||
assert.equal(ms(doc.activatedAt), JETZT.getTime())
|
||||
assert.equal(doc.disabledAt, null, 'die Loeschfrist endet')
|
||||
assert.equal(doc.disabledReason, null)
|
||||
assert.equal(doc.aboEndeMailAt, null)
|
||||
assert.equal(doc.adminNote, 'Wieder eingeschaltet: Wartungs-Abo läuft wieder')
|
||||
assert.equal(doc.unlockedByPurchase, true, 'bleibt ein bezahlter Terminplaner')
|
||||
assert.equal(doc.siteKey, 'abcdefghijklmn01', 'das eingebettete Snippet bleibt gueltig')
|
||||
assert.deepEqual(datenStand(), daten, 'Einstellung (samt Version) und Buchung sind unveraendert')
|
||||
assert.equal(aw.docs('termineConfig').get(F1).version, 7)
|
||||
|
||||
assert.equal(k23().length, 1)
|
||||
assert.equal(mails().length, 1)
|
||||
const mail = k23()[0]
|
||||
assert.equal(mail.subject, K23_BETREFF)
|
||||
assert.deepEqual(mail.to.map((a) => a.address), ['silke@example.com'])
|
||||
assert.match(mail.text, /Silke Müller/)
|
||||
assert.match(mail.text, /Dein Wartungs-Abo läuft wieder/)
|
||||
assert.match(mail.text, /Praxis Muster/)
|
||||
assert.match(mail.text, /https:\/\/project\.webklar\.com\/dashboard\.html#termine/)
|
||||
assert.ok(mail.bcc?.length, 'Kopie ans Team wie bei jeder Kundenmail')
|
||||
|
||||
// Folgetag: nichts mehr (das Dokument ist aktiv und das Abo laeuft), keine zweite Mail, nichts geschrieben
|
||||
const vorher = { ...funktion() }
|
||||
assert.deepEqual(await laufe(plus(JETZT, TAG)), ergebnis({ geprueft: 1 }))
|
||||
assert.deepEqual(funktion(), vorher)
|
||||
assert.equal(mails().length, 1)
|
||||
})
|
||||
|
||||
test('K22 ging nie (Marker leer, das Abo kam vorher zurueck): wieder eingeschaltet, weder K22 noch K23', async () => {
|
||||
legeAbgeschaltet(1, { aboEndeMailAt: null })
|
||||
wartung('active')
|
||||
assert.deepEqual(await laufe(), ergebnis({ geprueft: 1, wiederEin: 1 }))
|
||||
assert.equal(funktion().state, 'active')
|
||||
assert.equal(funktion().disabledReason, null)
|
||||
assert.equal(mails().length, 0, 'kein K22, kein K23')
|
||||
})
|
||||
|
||||
test('K22 scheiterte am Abschalttag und das Abo kommt am Folgetag zurueck: wieder aktiv, weder K22 noch K23 (der Kunde erfuhr nie vom Ende)', async () => {
|
||||
legeFunktion(1)
|
||||
legeDaten(1)
|
||||
wartung('canceled')
|
||||
setMailTransportForTests({ sendMail: async () => { throw new Error('Verbindung abgebrochen') } })
|
||||
assert.deepEqual(await laufe(JETZT), ergebnis({ geprueft: 1, abgeschaltet: 1, fehler: 1 }))
|
||||
assert.equal(funktion().state, 'disabled')
|
||||
assert.ok(!funktion().aboEndeMailAt)
|
||||
|
||||
setMailTransportForTests(sink)
|
||||
wartung('active')
|
||||
assert.deepEqual(await laufe(plus(JETZT, TAG)), ergebnis({ geprueft: 1, wiederEin: 1 }))
|
||||
assert.equal(funktion().state, 'active')
|
||||
assert.equal(mails().length, 0)
|
||||
})
|
||||
|
||||
test('Kunde ohne E-Mail oder geloescht: wieder eingeschaltet, kein K23, kein Fehler (ein Hinweis im Log, ohne Adresse)', async () => {
|
||||
for (const vorbereitung of [() => deleteDocument('customers', KUNDE), () => aw.seed('customers', { $id: KUNDE, name: 'Silke Müller', email: '' })]) {
|
||||
for (const id of aw.docs('portalFeatures').keys()) await deleteDocument('portalFeatures', id)
|
||||
legeAbgeschaltet(1)
|
||||
wartung('active')
|
||||
await vorbereitung()
|
||||
assert.deepEqual(await laufe(), ergebnis({ geprueft: 1, wiederEin: 1 }))
|
||||
assert.equal(funktion().state, 'active')
|
||||
assert.equal(mails().length, 0)
|
||||
}
|
||||
assert.ok(log.warn.mock.calls.length >= 1)
|
||||
})
|
||||
|
||||
test('Rueckkehr nach Tag 30 (disabledAt vor 35 Tagen), Einstellung noch da: wieder eingeschaltet; die Loeschung desselben Tagesjobs findet nichts mehr', async () => {
|
||||
const { loescheAbgeschalteteFunktionen } = await import('../server/services/termine/jobs.js')
|
||||
legeAbgeschaltet(1, { disabledAt: plus(JETZT, -35 * TAG).toISOString(), aboEndeMailAt: plus(JETZT, -35 * TAG).toISOString() })
|
||||
// Gegenprobe: ein Dokument mit Grund team in gleicher Lage wird von der Loeschung erfasst
|
||||
legeAbgeschaltet(2, { disabledReason: 'team', disabledAt: plus(JETZT, -35 * TAG).toISOString(), aboEndeMailAt: null })
|
||||
wartung('active')
|
||||
const daten = datenStand(1)
|
||||
|
||||
assert.deepEqual(await laufe(), ergebnis({ geprueft: 1, wiederEin: 1, mails: 1 }))
|
||||
assert.equal(funktion().state, 'active')
|
||||
assert.equal(funktion().disabledAt, null)
|
||||
assert.equal(k23().length, 1)
|
||||
|
||||
assert.deepEqual(await loescheAbgeschalteteFunktionen(JETZT), { funktionen: 1, buchungen: 1, gedeckelt: 0 }, 'nur das team-Dokument')
|
||||
assert.deepEqual(datenStand(1), daten, 'Einstellung und Buchung des zurueckgekehrten Terminplaners sind noch da')
|
||||
assert.equal(aw.docs('termineConfig').get(funktionsId(2)), undefined, 'Gegenprobe: team-Dokument geloescht')
|
||||
assert.equal(funktion().state, 'active')
|
||||
})
|
||||
|
||||
test('Nach der Loeschung nie automatisch: ein off-Dokument mit laufendem Abo bleibt unberuehrt und wird nicht einmal gelesen', async () => {
|
||||
legeFunktion(1, { state: 'off', enabled: false, unlockedByPurchase: false, purchaseStatus: 'zurueckgesetzt', adminNote: 'Daten gelöscht (Löschfrist abgelaufen)' })
|
||||
wartung('active')
|
||||
const vorher = { ...funktion() }
|
||||
assert.deepEqual(await laufe(), ergebnis())
|
||||
assert.deepEqual(funktion(), vorher)
|
||||
assert.equal(abfragenAufAbos(), 0)
|
||||
assert.equal(mails().length, 0)
|
||||
})
|
||||
|
||||
test('Ohne Einstellung (termineConfig fehlt oder ist unlesbar): bleibt abgeschaltet, kein K23, kein Fehler, nichts geschrieben', async () => {
|
||||
legeAbgeschaltet(1, {}, { daten: false })
|
||||
legeAbgeschaltet(2, {}, { daten: false })
|
||||
aw.seed('termineConfig', { $id: funktionsId(2), customerId: KUNDE, projectId: projektId(2), einstellung: '{kaputt', version: 3, updatedAt: JETZT.toISOString(), updatedBy: '' })
|
||||
wartung('active')
|
||||
const vorher = [1, 2].map((nr) => ({ ...funktion(funktionsId(nr)) }))
|
||||
assert.deepEqual(await laufe(), ergebnis({ geprueft: 2 }))
|
||||
assert.deepEqual([1, 2].map((nr) => funktion(funktionsId(nr))), vorher)
|
||||
assert.equal(mails().length, 0)
|
||||
})
|
||||
|
||||
test('Ein Kunde, zwei abgeschaltete Terminplaner: nur der mit Einstellung kehrt zurueck, K23 nennt sein Projekt', async () => {
|
||||
legeAbgeschaltet(1, {}, { projekt: 'Projekt Alpha' })
|
||||
legeAbgeschaltet(2, {}, { projekt: 'Projekt Beta', daten: false })
|
||||
wartung('active')
|
||||
assert.deepEqual(await laufe(), ergebnis({ geprueft: 2, wiederEin: 1, mails: 1 }))
|
||||
assert.equal(funktion(funktionsId(1)).state, 'active')
|
||||
assert.equal(funktion(funktionsId(2)).state, 'disabled')
|
||||
assert.deepEqual(mails().map((m) => m.subject), ['Dein Terminplaner läuft wieder – Projekt Alpha'])
|
||||
})
|
||||
|
||||
test('Nicht automatisch: team, kunde_geloescht, ohne Grund und abo_ende_vor_einrichtung bleiben trotz laufendem Abo und vorhandener Einstellung unberuehrt', async () => {
|
||||
const tag = plus(JETZT, -3 * TAG).toISOString()
|
||||
legeAbgeschaltet(1, { disabledReason: 'team', aboEndeMailAt: null, disabledAt: tag })
|
||||
legeAbgeschaltet(2, { disabledReason: 'kunde_geloescht', aboEndeMailAt: null, disabledAt: tag })
|
||||
legeAbgeschaltet(3, { disabledReason: undefined, aboEndeMailAt: null, disabledAt: tag }) // Altbestand ohne Grund
|
||||
legeAufbau(4, { state: 'disabled', disabledReason: 'abo_ende_vor_einrichtung', disabledAt: tag }) // bezahlt, Erstattung laeuft
|
||||
legeDaten(4)
|
||||
wartung('active')
|
||||
const vorher = [1, 2, 3, 4].map((nr) => ({ ...funktion(funktionsId(nr)) }))
|
||||
const daten = [1, 2, 3, 4].map((nr) => datenStand(nr))
|
||||
|
||||
assert.deepEqual(await laufe(), ergebnis({ geprueft: 1 }), 'nur abo_ende_vor_einrichtung zaehlt als bewertet')
|
||||
assert.deepEqual([1, 2, 3, 4].map((nr) => funktion(funktionsId(nr))), vorher, 'kein Feld wird angefasst')
|
||||
assert.deepEqual([1, 2, 3, 4].map((nr) => datenStand(nr)), daten)
|
||||
assert.equal(mails().length, 0, 'weder K23 noch K22 noch T19')
|
||||
})
|
||||
|
||||
// -- Rennen und Lesefehler: im Zweifel bleibt der Terminplaner abgeschaltet
|
||||
|
||||
test('Rennen: das Abo endet zwischen Vorauswahl und Sperre wieder - die frische Pruefung laesst den Terminplaner abgeschaltet, kein K23', async () => {
|
||||
legeAbgeschaltet(1)
|
||||
wartung('active')
|
||||
const vorher = { ...funktion() }
|
||||
const erg = await mitEinwurf(
|
||||
liste('subscriptions'),
|
||||
async () => wartung('canceled'), // die Vorauswahl las "laeuft", dann endet das Abo
|
||||
() => laufe(),
|
||||
)
|
||||
assert.deepEqual(erg, ergebnis({ geprueft: 1 }))
|
||||
assert.deepEqual(funktion(), vorher)
|
||||
assert.equal(mails().length, 0)
|
||||
assert.equal(abfragenAufAbos(), 2, 'Vorauswahl und frische Pruefung')
|
||||
})
|
||||
|
||||
test('Rennen: das Team schaltet zwischen Liste und Sperre selbst wieder ein oder mit Grund team ab - der Abgleich schreibt nichts, kein K23', async () => {
|
||||
for (const [fall, aenderung, erwartet] of [
|
||||
['Team aktiviert', (doc) => ({ ...doc, state: 'active', enabled: true, disabledAt: null, disabledReason: null, aboEndeMailAt: null }), { state: 'active', disabledReason: null }],
|
||||
['Team schaltet neu ab (Grund team)', (doc) => ({ ...doc, disabledReason: 'team', aboEndeMailAt: null }), { state: 'disabled', disabledReason: 'team' }],
|
||||
]) {
|
||||
for (const id of aw.docs('portalFeatures').keys()) await deleteDocument('portalFeatures', id)
|
||||
legeAbgeschaltet(1)
|
||||
wartung('active')
|
||||
let teamStand
|
||||
const erg = await mitEinwurf(
|
||||
liste('portalFeatures'),
|
||||
async () => {
|
||||
teamStand = aenderung({ ...funktion() })
|
||||
aw.seed('portalFeatures', teamStand)
|
||||
},
|
||||
() => laufe(),
|
||||
)
|
||||
assert.deepEqual(erg, ergebnis({ geprueft: 1 }), fall)
|
||||
assert.deepEqual(funktion(), teamStand, `${fall}: der Abgleich schreibt nichts`)
|
||||
assert.equal(funktion().state, erwartet.state, fall)
|
||||
assert.equal(funktion().disabledReason, erwartet.disabledReason, fall)
|
||||
assert.notEqual(funktion().adminNote, 'Wieder eingeschaltet: Wartungs-Abo läuft wieder', fall)
|
||||
assert.equal(mails().length, 0, fall)
|
||||
}
|
||||
})
|
||||
|
||||
test('Lesefehler beim Abo (Vorauswahl): bleibt abgeschaltet, fehler 1, kein K23; der naechste Lauf schaltet ein', async () => {
|
||||
legeAbgeschaltet(1)
|
||||
wartung('active')
|
||||
aw.failNext({ collection: 'subscriptions', method: 'GET', status: 500 })
|
||||
assert.deepEqual(await laufe(), ergebnis({ fehler: 1 }))
|
||||
assert.equal(funktion().state, 'disabled')
|
||||
assert.equal(mails().length, 0)
|
||||
|
||||
assert.deepEqual(await laufe(plus(JETZT, TAG)), ergebnis({ geprueft: 1, wiederEin: 1, mails: 1 }))
|
||||
assert.equal(funktion().state, 'active')
|
||||
})
|
||||
|
||||
test('Lesefehler erst bei der frischen Pruefung unter der Sperre: bleibt abgeschaltet, fehler 1', async () => {
|
||||
legeAbgeschaltet(1)
|
||||
wartung('active')
|
||||
aw.failNext({ collection: 'subscriptions', method: 'GET', status: 500, after: 1 })
|
||||
assert.deepEqual(await laufe(), ergebnis({ geprueft: 1, fehler: 1 }))
|
||||
assert.equal(funktion().state, 'disabled')
|
||||
assert.equal(funktion().disabledReason, 'abo_ende')
|
||||
assert.equal(mails().length, 0)
|
||||
})
|
||||
|
||||
test('Fehler beim Lesen der Ereignisse (Wechsel-Regel) oder der Einstellung: bleibt abgeschaltet, fehler 1, kein K23', async () => {
|
||||
legeAbgeschaltet(1)
|
||||
wartung('active')
|
||||
aw.failNext({ collection: 'contractEvents', method: 'GET', status: 500 })
|
||||
assert.deepEqual(await laufe(), ergebnis({ fehler: 1 }))
|
||||
assert.equal(funktion().state, 'disabled')
|
||||
|
||||
aw.failNext({ collection: 'termineConfig', method: 'GET', status: 500 })
|
||||
assert.deepEqual(await laufe(plus(JETZT, TAG)), ergebnis({ geprueft: 1, fehler: 1 }))
|
||||
assert.equal(funktion().state, 'disabled')
|
||||
assert.equal(mails().length, 0)
|
||||
|
||||
assert.deepEqual(await laufe(plus(JETZT, 2 * TAG)), ergebnis({ geprueft: 1, wiederEin: 1, mails: 1 }), 'ohne Fehler schaltet der naechste Lauf ein')
|
||||
})
|
||||
|
||||
test('Das Wiedereinschalten scheitert (PATCH): bleibt abgeschaltet, fehler 1 und kein K23 - die Mail folgt nur auf ein geschriebenes active', async () => {
|
||||
legeAbgeschaltet(1)
|
||||
wartung('active')
|
||||
const vorher = { ...funktion() }
|
||||
aw.failNext({ collection: 'portalFeatures', method: 'PATCH', status: 500 })
|
||||
assert.deepEqual(await laufe(), ergebnis({ geprueft: 1, fehler: 1 }))
|
||||
assert.deepEqual(funktion(), vorher)
|
||||
assert.equal(mails().length, 0)
|
||||
|
||||
assert.deepEqual(await laufe(plus(JETZT, TAG)), ergebnis({ geprueft: 1, wiederEin: 1, mails: 1 }))
|
||||
assert.equal(k23().length, 1)
|
||||
})
|
||||
|
||||
test('Sperre: haelt jemand feature-admin:<projectId>, wartet auch das Wiedereinschalten', async () => {
|
||||
legeAbgeschaltet(1)
|
||||
wartung('active')
|
||||
let fertig = false
|
||||
let lauf
|
||||
await withLocks(['feature-admin:p_projekt01'], async () => {
|
||||
lauf = gleicheAboAb(JETZT).then((erg) => {
|
||||
fertig = true
|
||||
return erg
|
||||
})
|
||||
await new Promise((resolve) => setTimeout(resolve, 250))
|
||||
assert.equal(fertig, false, 'der Abgleich wartet auf die Sperre')
|
||||
assert.equal(funktion().state, 'disabled')
|
||||
assert.equal(mails().length, 0)
|
||||
})
|
||||
assert.deepEqual(await lauf, ergebnis({ geprueft: 1, wiederEin: 1, mails: 1 }))
|
||||
assert.equal(funktion().state, 'active')
|
||||
})
|
||||
|
||||
test('K23 schlaegt fehl: der Terminplaner bleibt aktiv, fehler 1, wiederEin 1; am Folgetag wird nichts mehr angefasst', async () => {
|
||||
legeAbgeschaltet(1)
|
||||
wartung('active')
|
||||
setMailTransportForTests({
|
||||
sendMail: async (mail) => {
|
||||
if (/läuft wieder/.test(mail.subject)) throw new Error('550 5.1.1 <silke@example.com> Recipient address rejected')
|
||||
return sink.sendMail(mail)
|
||||
},
|
||||
})
|
||||
assert.deepEqual(await laufe(), ergebnis({ geprueft: 1, wiederEin: 1, fehler: 1 }))
|
||||
const doc = funktion()
|
||||
assert.equal(doc.state, 'active', 'die Reaktivierung bleibt bestehen')
|
||||
assert.equal(doc.disabledReason, null)
|
||||
assert.equal(doc.aboEndeMailAt, null)
|
||||
assert.equal(mails().length, 0)
|
||||
assert.ok(log.error.mock.calls.length >= 1, 'der Fehler steht im Log (ohne Adresse, das prueft afterEach)')
|
||||
|
||||
setMailTransportForTests(sink)
|
||||
const vorher = { ...funktion() }
|
||||
assert.deepEqual(await laufe(plus(JETZT, TAG)), ergebnis({ geprueft: 1 }))
|
||||
assert.deepEqual(funktion(), vorher)
|
||||
assert.equal(mails().length, 0)
|
||||
})
|
||||
|
||||
// -- Wechsel widerrufen (der Spiegel sagt noch "Wartung"): kein Flattern
|
||||
|
||||
test('Wechsel widerrufen: aktiv => abgeschaltet (K22 hosting), abgeschaltet bleibt abgeschaltet (kein K23); ein neueres, wirksames wechsel-Ereignis hebt das auf => Rueckkehr mit K23', async () => {
|
||||
legeFunktion(1)
|
||||
legeDaten(1)
|
||||
wartung('active') // der Spiegel steht auf Wartung, obwohl der Wechsel widerrufen wurde
|
||||
wechselEreignis('widerrufen')
|
||||
|
||||
assert.deepEqual(await laufe(JETZT), ergebnis({ geprueft: 1, abgeschaltet: 1, mails: 1 }))
|
||||
assert.equal(funktion().state, 'disabled')
|
||||
assert.equal(funktion().disabledReason, 'abo_ende')
|
||||
assert.match(k22()[0].text, /weil Dein Abo ein Hosting-Abo ist/)
|
||||
|
||||
// kein Flattern: am Folgetag und am uebernaechsten bleibt es abgeschaltet, ohne K23 und ohne zweite K22
|
||||
const vorher = { ...funktion() }
|
||||
assert.deepEqual(await laufe(plus(JETZT, TAG)), ergebnis({ geprueft: 1 }))
|
||||
assert.deepEqual(await laufe(plus(JETZT, 2 * TAG)), ergebnis({ geprueft: 1 }))
|
||||
assert.deepEqual(funktion(), vorher)
|
||||
assert.equal(k23().length, 0)
|
||||
assert.equal(k22().length, 1)
|
||||
|
||||
// ein neueres, wirksames wechsel-Ereignis (erledigt) hebt den Widerruf auf: das Wartungs-Abo laeuft wirklich
|
||||
wechselEreignis('erledigt')
|
||||
assert.deepEqual(await laufe(plus(JETZT, 3 * TAG)), ergebnis({ geprueft: 1, wiederEin: 1, mails: 1 }))
|
||||
assert.equal(funktion().state, 'active')
|
||||
assert.equal(funktion().disabledReason, null)
|
||||
assert.equal(k23().length, 1)
|
||||
assert.equal(k22().length, 1)
|
||||
})
|
||||
Reference in new issue
Block a user