Registereintrag config.features.shop (Bestelltexte mit Fassung config.shop.fassung, Rechtsunterlagen-Getter, Mailtexte, Ticket), STANDARD_HAKEN.shop (Schalter, Bestellpruefung ueber shop/anfrage.js, Unterlagen, Belehrung), legalDocs-Satz shop, shopTicketDetails sowie Rumpf von anfrageOffenePunkte. Tests angepasst und um Shop-Faelle erweitert. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
482 lines
22 KiB
JavaScript
482 lines
22 KiB
JavaScript
import { test, after, beforeEach, afterEach } from 'node:test'
|
||
import assert from 'node:assert/strict'
|
||
import { startAppwriteMock } from './support/appwriteMock.js'
|
||
import { useTestEnv } from './support/testEnv.js'
|
||
import { schemaKeys } from './support/schema.js'
|
||
|
||
// Freischaltung ueber das Register (Task B11): activateClosedTickets liest die Schluessel mit ticketFreischaltung aus
|
||
// config.features und fragt je Dokument unter der Sperre (nach dem frischen Lesen) den Haken freischaltbar. Termine:
|
||
// Einstellung vorhanden und laufendes Wartungs-Abo (Spec 4.5). Blockiert: das Dokument bleibt 'setup', EIN adminNote-Eintrag,
|
||
// keine Mail. websiteProjects, workorders und customers legt das Setup-Skript nicht an: ohne Schema im Nachbau.
|
||
const aw = await startAppwriteMock({
|
||
schema: {
|
||
portalFeatures: schemaKeys('portalFeatures'),
|
||
subscriptions: schemaKeys('subscriptions'),
|
||
termineConfig: schemaKeys('termineConfig'),
|
||
contractEvents: schemaKeys('contractEvents'),
|
||
},
|
||
})
|
||
after(() => aw.close())
|
||
useTestEnv({ appwriteUrl: aw.url, stripeUrl: 'http://127.0.0.1:1' })
|
||
const { config } = await import('../server/config.js')
|
||
const { seedFixtures, subscriptionDoc, wartungDoc } = await import('./support/fixtures.js')
|
||
const { deleteDocument } = await import('../server/services/appwriteAdmin.js')
|
||
const { withLocks } = await import('../server/services/keyedLock.js')
|
||
const { setMailTransportForTests } = await import('../server/services/mailer.js')
|
||
const { mailSink } = await import('./support/mailSink.js')
|
||
const reg = await import('../server/services/featureRegistry.js')
|
||
const sf = await import('../server/services/siteFeatures.js')
|
||
|
||
const mails = mailSink()
|
||
setMailTransportForTests(mails)
|
||
after(() => setMailTransportForTests(null))
|
||
|
||
const BASE = 'https://project.webklar.com'
|
||
const JETZT = new Date('2026-10-05T08:00:00.000Z')
|
||
const NOTIZ = /^\d{4}-\d\d-\d\dT[\d:.]+Z System: Freischaltung wartet: \S/
|
||
const TERMINE = config.features.termine
|
||
const NEWS = config.features.news
|
||
|
||
// seedFixtures gibt c1 nur ein Hosting-Abo (sub_h): Termine-Faelle ueberschreiben es mit einem Wartungs-Abo
|
||
const wartung = (patch = {}) => aw.seed('subscriptions', subscriptionDoc('sub_h', { ...wartungDoc, ...patch }))
|
||
const wartung2 = (patch = {}) =>
|
||
aw.seed('subscriptions', subscriptionDoc('sub_h2', { ...wartungDoc, customerId: 'c2', email: 'k2@example.invalid', stripeCustomerId: 'cus_c2', ...patch }))
|
||
|
||
let nr = 0
|
||
/**
|
||
* Dokument im Zustand 'setup' samt Projekt und Ticket (Standard: Ticket geschlossen). Liefert die ID des Dokuments.
|
||
* Termine: dazu optional die gespeicherte Einstellung (einstellung: true).
|
||
*/
|
||
function einrichtung(key, { customerId = 'c1', ticket = 'Closed', einstellung = false, ...over } = {}) {
|
||
nr += 1
|
||
const id = `f_${key}_${nr}`
|
||
const projectId = `p_${key}_${nr}`
|
||
aw.seed('websiteProjects', { $id: projectId, customerId, projectName: `Projekt ${nr}` })
|
||
aw.seed('workorders', { $id: `t_${id}`, woid: `WO-${nr}`, status: ticket, topic: 'x' })
|
||
aw.seed('portalFeatures', {
|
||
$id: id,
|
||
customerId,
|
||
projectId,
|
||
featureKey: key,
|
||
state: 'setup',
|
||
enabled: false,
|
||
siteKey: `siteke${String(nr).padStart(10, 'a')}`.slice(0, 16),
|
||
ticketId: `t_${id}`,
|
||
purchaseStatus: 'paid',
|
||
unlockedByPurchase: true,
|
||
...over,
|
||
})
|
||
if (einstellung) speichere(id, customerId, projectId)
|
||
return id
|
||
}
|
||
|
||
function speichere(featureId, customerId = 'c1', projectId = 'p') {
|
||
aw.seed('termineConfig', { $id: featureId, customerId, projectId, einstellung: JSON.stringify({ betrieb: { name: 'Salon' } }), version: 1, updatedAt: JETZT.toISOString(), updatedBy: 'test' })
|
||
}
|
||
|
||
const feld = (id, name) => aw.docs('portalFeatures').get(id)[name]
|
||
const zustand = (id) => feld(id, 'state')
|
||
const wartetEintraege = (id) => String(feld(id, 'adminNote') || '').split(' | ').filter((e) => e.includes('Freischaltung wartet'))
|
||
const k16 = (subject) => mails.sent.filter((m) => m.subject === subject)
|
||
|
||
const STUMM = (t) => {
|
||
t.mock.method(console, 'error', () => {})
|
||
t.mock.method(console, 'warn', () => {})
|
||
}
|
||
|
||
beforeEach(async () => {
|
||
for (const col of ['portalFeatures', 'workorders', 'termineConfig', 'subscriptions', 'websiteProjects', 'contractEvents']) {
|
||
for (const id of aw.docs(col).keys()) await deleteDocument(col, id)
|
||
}
|
||
seedFixtures(aw)
|
||
wartung()
|
||
mails.sent.length = 0
|
||
config.termine.mode = 'pilot'
|
||
config.termine.pilotEmails = ['*']
|
||
})
|
||
|
||
afterEach(() => {
|
||
delete TERMINE.freischaltbar
|
||
delete NEWS.freischaltbar
|
||
NEWS.ticketFreischaltung = true
|
||
})
|
||
|
||
// ---------------------------------------------------------------------------------------- Register ---
|
||
test('ticketFreischaltungKeys: Register-Schluessel mit ticketFreischaltung, in Registerreihenfolge', () => {
|
||
assert.deepEqual(reg.ticketFreischaltungKeys(), ['news', 'termine', 'shop'])
|
||
NEWS.ticketFreischaltung = false
|
||
assert.deepEqual(reg.ticketFreischaltungKeys(), ['termine', 'shop'])
|
||
NEWS.ticketFreischaltung = 'ja'
|
||
assert.deepEqual(reg.ticketFreischaltungKeys(), ['termine', 'shop'], 'nur true zaehlt')
|
||
NEWS.ticketFreischaltung = true
|
||
TERMINE.ticketFreischaltung = false
|
||
try {
|
||
assert.deepEqual(reg.ticketFreischaltungKeys(), ['news', 'shop'])
|
||
} finally {
|
||
TERMINE.ticketFreischaltung = true
|
||
}
|
||
})
|
||
|
||
test('Standard-Haken: termine hat freischaltbar, news nicht', () => {
|
||
assert.equal(typeof reg.featureHaken('termine').freischaltbar, 'function')
|
||
assert.equal(reg.featureHaken('news').freischaltbar, undefined)
|
||
})
|
||
|
||
// ------------------------------------------------------------------------------------------- News ---
|
||
test('News unveraendert: Ticket geschlossen => active, K16 der News-Pinnwand, ohne Abo, Einstellung oder Notiz', async () => {
|
||
const id = einrichtung('news')
|
||
const done = await sf.runActivation({ now: JETZT })
|
||
assert.deepEqual(done.map((d) => d.$id), [id])
|
||
assert.equal(zustand(id), 'active')
|
||
assert.equal(feld(id, 'enabled'), true)
|
||
assert.equal(feld(id, 'activatedAt'), JETZT.toISOString())
|
||
assert.ok(!feld(id, 'adminNote'), 'keine Notiz')
|
||
assert.equal(k16('Deine News-Pinnwand ist bereit').length, 1)
|
||
})
|
||
|
||
test('News: offenes Ticket bleibt setup', async () => {
|
||
const id = einrichtung('news', { ticket: 'Open' })
|
||
assert.deepEqual(await sf.activateClosedTickets({ now: JETZT }), [])
|
||
assert.equal(zustand(id), 'setup')
|
||
})
|
||
|
||
// ------------------------------------------------------------------------------ Termine: Freigabe ---
|
||
test('Termine: Ticket geschlossen + Einstellung + Abo => active, K16 "Dein Terminplaner ist bereit" mit Knopf "Termine oeffnen"', async () => {
|
||
const id = einrichtung('termine', { einstellung: true })
|
||
const done = await sf.runActivation({ now: JETZT })
|
||
assert.deepEqual(done.map((d) => d.$id), [id])
|
||
assert.equal(zustand(id), 'active')
|
||
assert.equal(feld(id, 'enabled'), true)
|
||
assert.equal(feld(id, 'activatedAt'), JETZT.toISOString())
|
||
assert.ok(!feld(id, 'adminNote'), 'frei => keine Wartenotiz')
|
||
|
||
const gesendet = k16('Dein Terminplaner ist bereit')
|
||
assert.equal(gesendet.length, 1, mails.sent.map((m) => m.subject).join(' / '))
|
||
assert.equal(mails.sent.length, 1, 'genau eine Mail')
|
||
const [mail] = gesendet
|
||
assert.deepEqual(mail.to.map((t) => t.address), ['k@example.invalid'])
|
||
assert.match(mail.text, /Hallo Kunde Eins,\nder Terminplaner ist auf Deiner Website eingebaut und freigeschaltet\./)
|
||
assert.match(mail.text, /Funktion: Terminplaner\nProjekt: Projekt \d+/)
|
||
assert.ok(mail.text.includes(`Termine öffnen: ${BASE}/dashboard.html#termine`), mail.text)
|
||
assert.match(mail.html, /href="https:\/\/project\.webklar\.com\/dashboard\.html#termine"/)
|
||
assert.doesNotMatch(mail.text, /News|Beitr/)
|
||
// zweiter Lauf: nichts mehr offen
|
||
assert.deepEqual(await sf.runActivation({ now: JETZT }), [])
|
||
assert.equal(mails.sent.length, 1)
|
||
})
|
||
|
||
test('Termine: Abo past_due zaehlt als laufend (wie laeuft)', async () => {
|
||
wartung({ status: 'past_due' })
|
||
const id = einrichtung('termine', { einstellung: true })
|
||
assert.equal((await sf.activateClosedTickets({ now: JETZT })).length, 1)
|
||
assert.equal(zustand(id), 'active')
|
||
})
|
||
|
||
test('Termine: kein Abo => bleibt setup, ein adminNote-Eintrag, keine Mail; Abo kommt zurueck => naechster Lauf aktiviert und sendet K16', async (t) => {
|
||
STUMM(t)
|
||
const id = einrichtung('termine', { einstellung: true })
|
||
// c1 hat nur ein Hosting-Abo: laeuft false
|
||
aw.seed('subscriptions', subscriptionDoc('sub_h', { ...wartungDoc, productKey: 'hosting' }))
|
||
|
||
assert.deepEqual(await sf.runActivation({ now: JETZT }), [])
|
||
assert.equal(zustand(id), 'setup')
|
||
assert.equal(feld(id, 'enabled'), false)
|
||
assert.equal(wartetEintraege(id).length, 1)
|
||
assert.match(feld(id, 'adminNote'), NOTIZ)
|
||
assert.equal(mails.sent.length, 0, 'keine Mail alle 5 Minuten')
|
||
|
||
// zweiter und dritter Lauf: weder ein zweiter Eintrag noch eine Mail
|
||
const notiz = feld(id, 'adminNote')
|
||
assert.deepEqual(await sf.runActivation({ now: new Date(JETZT.getTime() + 5 * 60000) }), [])
|
||
assert.deepEqual(await sf.runActivation({ now: new Date(JETZT.getTime() + 10 * 60000) }), [])
|
||
assert.equal(feld(id, 'adminNote'), notiz, 'Notiz unveraendert')
|
||
assert.equal(wartetEintraege(id).length, 1)
|
||
assert.equal(mails.sent.length, 0)
|
||
|
||
// Abo kommt zurueck
|
||
wartung()
|
||
const done = await sf.runActivation({ now: JETZT })
|
||
assert.deepEqual(done.map((d) => d.$id), [id])
|
||
assert.equal(zustand(id), 'active')
|
||
assert.equal(k16('Dein Terminplaner ist bereit').length, 1)
|
||
assert.equal(mails.sent.length, 1)
|
||
assert.deepEqual(await sf.runActivation({ now: JETZT }), [])
|
||
assert.equal(mails.sent.length, 1)
|
||
})
|
||
|
||
test('Termine: Abo beendet (canceled) => bleibt setup', async (t) => {
|
||
STUMM(t)
|
||
wartung({ status: 'canceled' })
|
||
const id = einrichtung('termine', { einstellung: true })
|
||
assert.deepEqual(await sf.runActivation({ now: JETZT }), [])
|
||
assert.equal(zustand(id), 'setup')
|
||
assert.match(feld(id, 'adminNote'), /Freischaltung wartet/)
|
||
assert.equal(mails.sent.length, 0)
|
||
})
|
||
|
||
test('Termine: Wechsel-Widerruf (Spiegel Wartung active, juengstes wechsel-Ereignis widerrufen) => bleibt setup, keine Mail; ein neueres erledigtes Ereignis gibt frei', async (t) => {
|
||
STUMM(t)
|
||
const wechsel = (nr, status) =>
|
||
aw.seed('contractEvents', {
|
||
$id: `ev_w${nr}`,
|
||
$createdAt: new Date(Date.UTC(2026, 8, 1, 0, 0, nr)).toISOString(),
|
||
customerId: 'c1',
|
||
subscriptionId: 'sub_h',
|
||
type: 'wechsel',
|
||
productKey: 'wartung',
|
||
details: '{}',
|
||
status,
|
||
})
|
||
const id = einrichtung('termine', { einstellung: true })
|
||
wechsel(1, 'widerrufen')
|
||
assert.deepEqual(await sf.runActivation({ now: JETZT }), [])
|
||
assert.equal(zustand(id), 'setup')
|
||
assert.equal(feld(id, 'enabled'), false)
|
||
assert.equal(wartetEintraege(id).length, 1)
|
||
assert.equal(mails.sent.length, 0)
|
||
|
||
wechsel(2, 'erledigt')
|
||
assert.deepEqual((await sf.runActivation({ now: JETZT })).map((d) => d.$id), [id])
|
||
assert.equal(zustand(id), 'active')
|
||
assert.equal(k16('Dein Terminplaner ist bereit').length, 1)
|
||
})
|
||
|
||
test('Termine: keine Einstellung => bleibt setup (mit Notiz), mit gespeicherter Einstellung aktiviert der naechste Lauf', async (t) => {
|
||
STUMM(t)
|
||
const id = einrichtung('termine')
|
||
assert.deepEqual(await sf.runActivation({ now: JETZT }), [])
|
||
assert.equal(zustand(id), 'setup')
|
||
assert.equal(wartetEintraege(id).length, 1)
|
||
assert.deepEqual(await sf.runActivation({ now: JETZT }), [])
|
||
assert.equal(wartetEintraege(id).length, 1, 'kein zweiter Eintrag')
|
||
assert.equal(mails.sent.length, 0)
|
||
|
||
speichere(id, 'c1', feld(id, 'projectId'))
|
||
assert.equal((await sf.runActivation({ now: JETZT })).length, 1)
|
||
assert.equal(zustand(id), 'active')
|
||
assert.equal(k16('Dein Terminplaner ist bereit').length, 1)
|
||
})
|
||
|
||
test('Termine: aendert sich der Grund, kommt ein neuer Eintrag; Notiz bleibt auf 300 Zeichen und aeltere Eintraege bleiben stehen', async (t) => {
|
||
STUMM(t)
|
||
const id = einrichtung('termine', { adminNote: '2026-10-01T10:00:00.000Z Andrej: activate – Kulanz' })
|
||
await sf.activateClosedTickets({ now: JETZT })
|
||
assert.equal(wartetEintraege(id).length, 1)
|
||
assert.match(feld(id, 'adminNote'), /\| 2026-10-01T10:00:00\.000Z Andrej: activate – Kulanz$/, 'aelterer Eintrag bleibt')
|
||
assert.ok(feld(id, 'adminNote').startsWith(JETZT.toISOString()), 'neuester Eintrag vorn, mit der Zeit des Laufs')
|
||
assert.ok(feld(id, 'adminNote').length <= 300)
|
||
})
|
||
|
||
// ------------------------------------------------------------------------------- Termine: Fehler ---
|
||
test('Abo-Lesefehler => bleibt setup, runActivation wirft nicht, andere Dokumente werden trotzdem bearbeitet', async (t) => {
|
||
STUMM(t)
|
||
const termine = einrichtung('termine', { einstellung: true })
|
||
const news = einrichtung('news')
|
||
aw.failNext({ collection: 'subscriptions', status: 500 })
|
||
const done = await sf.runActivation({ now: JETZT })
|
||
assert.deepEqual(done.map((d) => d.$id), [news])
|
||
assert.equal(zustand(termine), 'setup', 'nie freischalten, wenn das Abo nicht lesbar war')
|
||
assert.equal(zustand(news), 'active')
|
||
assert.match(feld(termine, 'adminNote'), /Freischaltung wartet/)
|
||
assert.equal(k16('Deine News-Pinnwand ist bereit').length, 1)
|
||
assert.equal(k16('Dein Terminplaner ist bereit').length, 0)
|
||
const geloggt = console.error.mock.calls.map((c) => c.arguments.join(' ')).join('\n')
|
||
assert.match(geloggt, /Freischaltung termine \(f_termine_\d+\) nicht prüfbar/, 'Fehler wird geloggt')
|
||
|
||
// der naechste Lauf liest wieder und aktiviert
|
||
const spaeter = await sf.runActivation({ now: JETZT })
|
||
assert.deepEqual(spaeter.map((d) => d.$id), [termine])
|
||
assert.equal(zustand(termine), 'active')
|
||
})
|
||
|
||
test('Lesefehler bei der Einstellung (termineConfig) => bleibt setup, kein Wurf', async (t) => {
|
||
STUMM(t)
|
||
const id = einrichtung('termine', { einstellung: true })
|
||
aw.failNext({ collection: 'termineConfig', status: 500 })
|
||
assert.deepEqual(await sf.runActivation({ now: JETZT }), [])
|
||
assert.equal(zustand(id), 'setup')
|
||
assert.equal(mails.sent.length, 0)
|
||
})
|
||
|
||
test('Haken wirft => bleibt setup (Log, Notiz), die uebrigen Dokumente werden bearbeitet', async (t) => {
|
||
STUMM(t)
|
||
TERMINE.freischaltbar = async () => {
|
||
throw new Error('Pruefung kaputt')
|
||
}
|
||
const termine = einrichtung('termine', { einstellung: true })
|
||
const news = einrichtung('news')
|
||
assert.deepEqual((await sf.runActivation({ now: JETZT })).map((d) => d.$id), [news])
|
||
assert.equal(zustand(termine), 'setup')
|
||
assert.match(feld(termine, 'adminNote'), /Freischaltung wartet/)
|
||
assert.match(console.error.mock.calls.map((c) => c.arguments.join(' ')).join('\n'), /Pruefung kaputt/)
|
||
})
|
||
|
||
test('Haken meldet etwas anderes als true (false, undefined, Text) => bleibt setup (fail-closed)', async (t) => {
|
||
STUMM(t)
|
||
const id = einrichtung('termine', { einstellung: true })
|
||
for (const antwort of [false, undefined, null, 'ja', 1]) {
|
||
TERMINE.freischaltbar = async () => antwort
|
||
assert.deepEqual(await sf.activateClosedTickets({ now: JETZT }), [], String(antwort))
|
||
assert.equal(zustand(id), 'setup', String(antwort))
|
||
}
|
||
})
|
||
|
||
test('Schreiben der Wartenotiz scheitert => kein Wurf, das naechste Dokument wird trotzdem bearbeitet', async (t) => {
|
||
STUMM(t)
|
||
const termine = einrichtung('termine')
|
||
const news = einrichtung('news')
|
||
aw.failNext({ collection: 'portalFeatures', method: 'PATCH', status: 500 })
|
||
const done = await sf.activateClosedTickets({ now: JETZT })
|
||
assert.deepEqual(done.map((d) => d.$id), [news])
|
||
assert.equal(zustand(termine), 'setup')
|
||
assert.equal(zustand(news), 'active')
|
||
})
|
||
|
||
// ------------------------------------------------------------------------ Sperre und frisches Lesen ---
|
||
test('Abo endet zwischen Listenlesen und Sperre (der Haken meldet beim frischen Lesen false) => bleibt setup', async (t) => {
|
||
STUMM(t)
|
||
const id = einrichtung('termine', { einstellung: true })
|
||
let lauf
|
||
await withLocks([`feature-activate:${id}`], async () => {
|
||
// der Lauf liest die Liste und das Ticket und wartet dann auf die Sperre
|
||
lauf = sf.activateClosedTickets({ now: JETZT })
|
||
for (let i = 0; i < 100 && aw.listCalls('portalFeatures').length === 0; i += 1) await new Promise((r) => setTimeout(r, 5))
|
||
await new Promise((r) => setTimeout(r, 50))
|
||
wartung({ status: 'canceled' })
|
||
})
|
||
assert.deepEqual(await lauf, [])
|
||
assert.equal(zustand(id), 'setup')
|
||
assert.match(feld(id, 'adminNote'), /Freischaltung wartet/)
|
||
})
|
||
|
||
test('Der Haken bekommt das unter der Sperre frisch gelesene Dokument; ein zwischendurch aktiviertes wird nicht noch einmal angefasst', async () => {
|
||
const gesehen = []
|
||
TERMINE.freischaltbar = async (doc) => {
|
||
gesehen.push({ state: doc.state, marker: doc.adminNote })
|
||
return true
|
||
}
|
||
const frisch = einrichtung('termine')
|
||
const schonAktiv = einrichtung('termine')
|
||
let lauf
|
||
await withLocks([`feature-activate:${frisch}`, `feature-activate:${schonAktiv}`], async () => {
|
||
lauf = sf.activateClosedTickets({ now: JETZT })
|
||
for (let i = 0; i < 100 && aw.listCalls('portalFeatures').length === 0; i += 1) await new Promise((r) => setTimeout(r, 5))
|
||
await new Promise((r) => setTimeout(r, 50))
|
||
aw.seed('portalFeatures', { ...aw.docs('portalFeatures').get(frisch), adminNote: 'frisch gelesen' })
|
||
aw.seed('portalFeatures', { ...aw.docs('portalFeatures').get(schonAktiv), state: 'active', enabled: true })
|
||
})
|
||
const done = await lauf
|
||
assert.deepEqual(done.map((d) => d.$id), [frisch])
|
||
assert.deepEqual(gesehen, [{ state: 'setup', marker: 'frisch gelesen' }], 'der Haken sieht nur das frische, noch offene Dokument')
|
||
})
|
||
|
||
test('A3: disable() teilt die Sperre feature-activate:<id> mit der Freischaltung - wer die Freischaltung haelt, laesst das Abschalten warten', async () => {
|
||
const id = einrichtung('termine', { einstellung: true })
|
||
let abschalten
|
||
await withLocks([`feature-activate:${id}`], async () => {
|
||
abschalten = sf.disable(id, { reason: 'team' })
|
||
await new Promise((r) => setTimeout(r, 80))
|
||
assert.equal(zustand(id), 'setup', 'solange die Freischaltung die Sperre haelt, schaltet disable() nicht ab')
|
||
})
|
||
const doc = await abschalten
|
||
assert.equal(doc.state, 'disabled')
|
||
assert.equal(zustand(id), 'disabled')
|
||
})
|
||
|
||
// ------------------------------------------------------------------------------- Tickets, Kunden ---
|
||
test('Ticket Cancelled (oder Open) => bleibt setup, der Haken wird gar nicht gefragt', async () => {
|
||
let gefragt = 0
|
||
TERMINE.freischaltbar = async () => {
|
||
gefragt += 1
|
||
return true
|
||
}
|
||
const a = einrichtung('termine', { einstellung: true, ticket: 'Cancelled' })
|
||
const b = einrichtung('termine', { einstellung: true, ticket: 'Open' })
|
||
assert.deepEqual(await sf.runActivation({ now: JETZT }), [])
|
||
assert.equal(zustand(a), 'setup')
|
||
assert.equal(zustand(b), 'setup')
|
||
assert.equal(gefragt, 0)
|
||
assert.equal(mails.sent.length, 0)
|
||
})
|
||
|
||
test('customerId-Filter: nur Dokumente dieses Kunden', async () => {
|
||
wartung2()
|
||
const eins = einrichtung('termine', { einstellung: true })
|
||
const zwei = einrichtung('termine', { einstellung: true, customerId: 'c2' })
|
||
const done = await sf.runActivation({ now: JETZT, customerId: 'c1' })
|
||
assert.deepEqual(done.map((d) => d.$id), [eins])
|
||
assert.equal(zustand(eins), 'active')
|
||
assert.equal(zustand(zwei), 'setup', 'anderer Kunde bleibt liegen')
|
||
assert.deepEqual(mails.sent.map((m) => m.to[0].address), ['k@example.invalid'])
|
||
// ohne Filter kommt der andere Kunde dran
|
||
assert.deepEqual((await sf.runActivation({ now: JETZT })).map((d) => d.$id), [zwei])
|
||
assert.deepEqual(mails.sent.map((m) => m.to[0].address), ['k@example.invalid', 'k2@example.invalid'])
|
||
})
|
||
|
||
// -------------------------------------------------------------------------------- Haken je Funktion ---
|
||
test('Der Haken gilt nur fuer seine Funktion: ein News-Dokument im selben Lauf wird aktiviert', async (t) => {
|
||
STUMM(t)
|
||
const gefragt = []
|
||
TERMINE.freischaltbar = async (doc) => {
|
||
gefragt.push(doc.featureKey)
|
||
return false
|
||
}
|
||
const termine = einrichtung('termine', { einstellung: true })
|
||
const news = einrichtung('news')
|
||
const done = await sf.runActivation({ now: JETZT })
|
||
assert.deepEqual(done.map((d) => d.$id), [news])
|
||
assert.equal(zustand(termine), 'setup')
|
||
assert.equal(zustand(news), 'active')
|
||
assert.deepEqual(gefragt, ['termine'], 'der Termine-Haken wird nie fuer News gefragt')
|
||
})
|
||
|
||
test('Ein Haken im Register ueberschreibt den Standard: true gibt frei, auch ohne Abo und Einstellung', async () => {
|
||
aw.seed('subscriptions', subscriptionDoc('sub_h', { ...wartungDoc, productKey: 'hosting' }))
|
||
TERMINE.freischaltbar = async () => true
|
||
const id = einrichtung('termine')
|
||
assert.equal((await sf.runActivation({ now: JETZT })).length, 1)
|
||
assert.equal(zustand(id), 'active')
|
||
assert.equal(k16('Dein Terminplaner ist bereit').length, 1)
|
||
})
|
||
|
||
test('Funktionen ohne ticketFreischaltung im Register werden nicht geoeffnet', async () => {
|
||
NEWS.ticketFreischaltung = false
|
||
const news = einrichtung('news')
|
||
assert.deepEqual(await sf.activateClosedTickets({ now: JETZT }), [])
|
||
assert.equal(zustand(news), 'setup')
|
||
})
|
||
|
||
test('Dokument einer anderen Funktion mit setup (analyse) bleibt unberuehrt', async () => {
|
||
const analyse = einrichtung('analyse')
|
||
const unbekannt = einrichtung('gibtsnicht')
|
||
const termine = einrichtung('termine', { einstellung: true })
|
||
const done = await sf.runActivation({ now: JETZT })
|
||
assert.deepEqual(done.map((d) => d.$id), [termine])
|
||
for (const id of [analyse, unbekannt]) {
|
||
assert.equal(zustand(id), 'setup', id)
|
||
assert.equal(feld(id, 'enabled'), false, id)
|
||
assert.ok(!feld(id, 'adminNote'), id)
|
||
}
|
||
assert.equal(mails.sent.length, 1)
|
||
})
|
||
|
||
// --------------------------------------------------------------------------------- parallele Laeufe ---
|
||
test('Parallele Laeufe (runActivation() und runActivation({ customerId })) => ein Terminplaner-K16', async () => {
|
||
const id = einrichtung('termine', { einstellung: true })
|
||
const [a, b] = await Promise.all([sf.runActivation({ now: JETZT }), sf.runActivation({ now: JETZT, customerId: 'c1' })])
|
||
assert.equal(a.length + b.length, 1)
|
||
assert.equal(zustand(id), 'active')
|
||
assert.equal(k16('Dein Terminplaner ist bereit').length, 1)
|
||
assert.equal(mails.sent.length, 1)
|
||
})
|
||
|
||
test('Parallele Laeufe ohne Abo => hoechstens eine Wartenotiz, keine Mail', async (t) => {
|
||
STUMM(t)
|
||
aw.seed('subscriptions', subscriptionDoc('sub_h', { ...wartungDoc, productKey: 'hosting' }))
|
||
const id = einrichtung('termine', { einstellung: true })
|
||
await Promise.all([sf.runActivation({ now: JETZT }), sf.runActivation({ now: JETZT, customerId: 'c1' }), sf.runActivation({ now: JETZT })])
|
||
assert.equal(zustand(id), 'setup')
|
||
assert.equal(wartetEintraege(id).length, 1)
|
||
assert.equal(mails.sent.length, 0)
|
||
})
|