115 lines
6.6 KiB
JavaScript
115 lines
6.6 KiB
JavaScript
import { test, after, beforeEach } from 'node:test'
|
|
import assert from 'node:assert/strict'
|
|
import { readFileSync } from 'node:fs'
|
|
import { startAppwriteMock } from './support/appwriteMock.js'
|
|
import { startFakeStripe } from './support/fakeStripe.js'
|
|
import { useTestEnv } from './support/testEnv.js'
|
|
import { schemaKeys } from './support/schema.js'
|
|
import { AUFTRAEGE_DATETIMES } from '../server/services/auftraege/schema.js'
|
|
|
|
// Gesammelte Widerrufsfristen (V6): Abo-Abschluss, Wechsel, Zusatzspeicher (nur Plus), bezahlte Funktionen, Auftraege
|
|
const aw = await startAppwriteMock({
|
|
schema: {
|
|
subscriptions: schemaKeys('subscriptions'),
|
|
contractEvents: schemaKeys('contractEvents'),
|
|
portalFeatures: schemaKeys('portalFeatures'),
|
|
featureConsents: schemaKeys('featureConsents'),
|
|
auftraege: schemaKeys('auftraege'),
|
|
},
|
|
datetimes: AUFTRAEGE_DATETIMES,
|
|
})
|
|
const st = await startFakeStripe()
|
|
useTestEnv({ appwriteUrl: aw.url, stripeUrl: st.url })
|
|
const { config } = await import('../server/config.js')
|
|
const { startTestApp, rawGet } = await import('./support/testApp.js')
|
|
const { customers, customerCookie, kundenansichtCookie, subscriptionDoc, wartungDoc } = await import('./support/fixtures.js')
|
|
const { deleteDocument } = await import('../server/services/appwriteAdmin.js')
|
|
const { sammleFristen } = await import('../server/services/fristen.js')
|
|
const { default: fristenRoutes } = await import('../server/routes/fristen.js')
|
|
const app = await startTestApp({ '/api/fristen': fristenRoutes })
|
|
const modusVorher = config.auftraege.mode
|
|
after(() => {
|
|
config.auftraege.mode = modusVorher
|
|
return Promise.all([app.close(), aw.close(), st.close()])
|
|
})
|
|
|
|
const JETZT = new Date('2026-10-17T08:00:00.000Z')
|
|
const ereignis = (id, type, contractAt, over = {}) =>
|
|
aw.seed('contractEvents', { $id: id, customerId: 'c1', subscriptionId: 'sub_h', type, productKey: 'wartung', status: 'erledigt', consentAt: contractAt, details: JSON.stringify({ contractAt }), ...over })
|
|
const aufbau = () => { // Ende je Quelle: wechsel Mo 26.10., funktion Do 29.10., speicher Fr 30.10., abschluss Mo 02.11.
|
|
ereignis('ev_a', 'abschluss', '2026-10-17T07:00:00.000Z')
|
|
ereignis('ev_w', 'wechsel', '2026-10-10T09:00:00.000Z')
|
|
ereignis('ev_s', 'speicher', '2026-10-16T09:00:00.000Z', { details: JSON.stringify({ contractAt: '2026-10-16T09:00:00.000Z', fromUnits: 0, toUnits: 1 }) })
|
|
ereignis('ev_m', 'speicher', '2026-10-16T10:00:00.000Z', { details: JSON.stringify({ contractAt: '2026-10-16T10:00:00.000Z', fromUnits: 2, toUnits: 1 }) }) // Minus: keine Frist
|
|
aw.seed('portalFeatures', { $id: 'f_p1_news', customerId: 'c1', projectId: 'p1', featureKey: 'news', state: 'setup', enabled: false, unlockedByPurchase: true, orderedAt: '2026-10-15T09:00:00.000Z' })
|
|
}
|
|
|
|
beforeEach(async () => {
|
|
for (const collection of ['contractEvents', 'portalFeatures', 'auftraege', 'subscriptions', 'websiteProjects']) {
|
|
for (const id of aw.docs(collection).keys()) await deleteDocument(collection, id)
|
|
}
|
|
aw.seed('customers', { ...customers.c1 })
|
|
aw.seed('subscriptions', subscriptionDoc('sub_h', wartungDoc))
|
|
aw.seed('websiteProjects', { $id: 'p1', customerId: 'c1', projectName: 'Bäckerei' })
|
|
config.auftraege.mode = 'off'
|
|
})
|
|
|
|
test('mehrere Quellen, aufsteigend nach Ende; Speicher-Plus zählt, Minus nicht', async () => {
|
|
aufbau()
|
|
const r = await sammleFristen('c1', { now: JETZT })
|
|
assert.deepEqual(r.fristen, [
|
|
{ quelle: 'wechsel', bezeichnung: 'Wechsel ins Wartungs-Abo', bis: '2026-10-26T22:59:59.999Z' },
|
|
{ quelle: 'funktion', bezeichnung: 'News-Pinnwand', bis: '2026-10-29T22:59:59.999Z', featureKey: 'news', projektName: 'Bäckerei' },
|
|
{ quelle: 'speicher', bezeichnung: 'Zusatzspeicher', bis: '2026-10-30T22:59:59.999Z' },
|
|
{ quelle: 'abschluss', bezeichnung: 'Abo-Abschluss', bis: '2026-11-02T22:59:59.999Z' },
|
|
])
|
|
assert.deepEqual([r.fruehestesEnde, r.anzeigen], ['2026-10-26T22:59:59.999Z', true])
|
|
})
|
|
|
|
test('Kundenart: Betrieb hat Fristen, aber anzeigen false; privat, leer und unbekannt zeigen', async () => {
|
|
aufbau()
|
|
for (const [kundenart, anzeigen] of [['unternehmen', false], ['privat', true], ['', true], ['kaputt', true]]) {
|
|
aw.seed('customers', { ...customers.c1, kundenart })
|
|
const r = await sammleFristen('c1', { now: JETZT })
|
|
assert.deepEqual([r.fristen.length, r.anzeigen], [4, anzeigen], kundenart)
|
|
}
|
|
})
|
|
|
|
test('abgelaufene oder fehlende Fristen: leer, fruehestesEnde null, anzeigen false', async () => {
|
|
ereignis('ev_alt', 'abschluss', '2026-09-20T09:00:00.000Z') // Ende Mo 05.10.
|
|
assert.deepEqual(await sammleFristen('c1', { now: JETZT }), { fristen: [], fruehestesEnde: null, anzeigen: false })
|
|
})
|
|
|
|
test('Aufträge zählen nur mit Schalter und Snapshot-Kundenart privat', async () => {
|
|
const ab = (kundenart, status = 'versendet') =>
|
|
aw.seed('auftraege', { $id: 'ab1', nummer: 'AB-2026-0007', kundenId: 'c1', status, gesendetAm: '2026-10-16T09:00:00.000Z', kundeJson: JSON.stringify({ kundenart }) })
|
|
ab('privat')
|
|
config.auftraege.mode = 'off'
|
|
assert.deepEqual((await sammleFristen('c1', { now: JETZT })).fristen, [])
|
|
config.auftraege.mode = 'on'
|
|
assert.deepEqual((await sammleFristen('c1', { now: JETZT })).fristen, [{ quelle: 'projekt', bezeichnung: 'Auftrag AB-2026-0007', bis: '2026-10-30T22:59:59.999Z' }])
|
|
ab('privat', 'bestaetigt')
|
|
assert.equal((await sammleFristen('c1', { now: JETZT })).fristen.length, 1)
|
|
ab('privat', 'widerrufen')
|
|
assert.deepEqual((await sammleFristen('c1', { now: JETZT })).fristen, [])
|
|
ab('unternehmen')
|
|
assert.deepEqual((await sammleFristen('c1', { now: JETZT })).fristen, [])
|
|
})
|
|
|
|
test('Lesefehler werden weitergereicht', async () => {
|
|
aufbau()
|
|
aw.failNext({ collection: 'contractEvents', method: 'GET', status: 500 })
|
|
await assert.rejects(() => sammleFristen('c1', { now: JETZT }))
|
|
})
|
|
|
|
test('GET /api/fristen: 401 ohne Anmeldung, Form, no-store, Kundenansicht erlaubt; Mount in server/index.js', async () => {
|
|
assert.equal((await rawGet('/api/fristen')).status, 401)
|
|
const res = await rawGet('/api/fristen', { cookie: customerCookie('c1') })
|
|
assert.deepEqual([res.status, Object.keys(res.body).sort(), res.headers.get('cache-control')], [200, ['anzeigen', 'fristen', 'fruehestesEnde'], 'no-store'])
|
|
assert.equal((await rawGet('/api/fristen', { cookie: kundenansichtCookie({ customerId: 'c1' }) })).status, 200)
|
|
const src = readFileSync(new URL('../server/index.js', import.meta.url), 'utf8')
|
|
assert.ok(src.indexOf("app.use('/api/fristen', fristenRoutes)") > src.indexOf('app.use(sessionMiddleware())'))
|
|
assert.ok(src.indexOf("app.use('/api/fristen', fristenRoutes)") < src.indexOf('app.use(express.static(publicDir))'))
|
|
assert.ok(src.indexOf("app.use('/api/fristen', fristenRoutes)") > src.indexOf("app.use('/api/funktionen', funktionenRoutes)"))
|
|
})
|