- routes/shop.js: gemeinsame Pruefung mitShop (Schalter, eigenes Projekt, Funktion setup|active, lesend auch disabled, shopConfig bei Bedarf anlegen); GET /status erweitert (Bereitschaft, geoeffnet, Snippet, Vorschau, Stripe ohne Konto-ID, Zahlen); Einstellungen, Artikel (mit Bild-Pruefung, Beanspruchen und Aufraeumen), Sortierung, Rechtstexte (Entwurf, Bestaetigen mit Hash), Oeffnen/Schliessen, Bild-Upload (octet-stream, 8 MB) und Vorschau; Schreib-Limit 300 je Stunde und Kunde - services/shop/bilder.js: Artikelbilder nach dem Muster von newsImages.js (1600/480 px WebP, Sidecar, 24-h-Aufraeumen, 40 Uploads/h) - Kundenansicht: sieben Verwaltungs-Pfade erlaubt und als shop_aenderung protokolliert; Anfrage, Oeffnen, Bestaetigen, Stripe bleiben gesperrt - Tests: shopRoutes, shopBilder; Erwartungen von kundenansichtGuard, customerViewLog und shopAnfrage angepasst Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
77 lines
3.9 KiB
JavaScript
77 lines
3.9 KiB
JavaScript
import { test, after } from 'node:test'
|
|
import assert from 'node:assert/strict'
|
|
import { startAppwriteMock } from './support/appwriteMock.js'
|
|
import { startFakeStripe } from './support/fakeStripe.js'
|
|
import { useTestEnv } from './support/testEnv.js'
|
|
import { schemaKeys } from './support/schema.js'
|
|
|
|
// Protokoll der Kundenansicht (Collection customerViewLog): schreiben, lesen, nach 12 Monaten loeschen
|
|
const aw = await startAppwriteMock({ schema: { customerViewLog: schemaKeys('customerViewLog') } })
|
|
const st = await startFakeStripe()
|
|
useTestEnv({ appwriteUrl: aw.url, stripeUrl: st.url })
|
|
const { VIEW_ACTIONS, logViewAction, listViewLog, purgeViewLog } = await import('../server/services/customerViewLog.js')
|
|
after(() => Promise.all([aw.close(), st.close()]))
|
|
|
|
const DAY_MS = 24 * 60 * 60 * 1000
|
|
const entries = () => [...aw.docs('customerViewLog').values()]
|
|
const seedLog = (id, customerId, createdAt, action = 'geoeffnet') =>
|
|
aw.seed('customerViewLog', { $id: id, customerId, staffUserId: 'u_staff2', staffName: 'Andrej', action, detail: '', createdAt })
|
|
|
|
test('VIEW_ACTIONS: genau die elf Aktionen', () => {
|
|
assert.deepEqual(VIEW_ACTIONS, [
|
|
'geoeffnet', 'beendet', 'abgelaufen', 'news_angelegt', 'news_bild', 'news_geaendert', 'news_geloescht',
|
|
'termin_einstellungen', 'termin_aktion', 'termin_manuell', 'shop_aenderung',
|
|
])
|
|
})
|
|
|
|
test('logViewAction schreibt alle Felder und kuerzt detail auf 300', async () => {
|
|
const before = entries().length
|
|
await logViewAction({ customerId: 'c9', staffUserId: 'u_staff2', staffName: 'Andrej', action: 'news_angelegt', detail: 'x'.repeat(400) })
|
|
const all = entries()
|
|
assert.equal(all.length, before + 1)
|
|
const doc = all.find((d) => d.customerId === 'c9')
|
|
assert.equal(doc.detail.length, 300)
|
|
assert.equal(doc.action, 'news_angelegt')
|
|
assert.equal(doc.staffName, 'Andrej')
|
|
assert.equal(doc.staffUserId, 'u_staff2')
|
|
assert.ok(Math.abs(Date.parse(doc.createdAt) - Date.now()) < 5000)
|
|
})
|
|
|
|
test('logViewAction wirft nicht, wenn Appwrite scheitert', async (t) => {
|
|
const warn = t.mock.method(console, 'warn', () => {})
|
|
aw.failNext({ collection: 'customerViewLog', method: 'POST', status: 500, message: 'kaputt' })
|
|
await assert.doesNotReject(() => logViewAction({ customerId: 'c8', staffUserId: 'u', staffName: 'X', action: 'geoeffnet' }))
|
|
assert.equal(entries().filter((d) => d.customerId === 'c8').length, 0)
|
|
assert.equal(warn.mock.callCount(), 1)
|
|
})
|
|
|
|
test('listViewLog: nur dieser Kunde, neueste zuerst, Form {id, at, staffName, action, detail}', async () => {
|
|
seedLog('l1', 'c1', '2026-09-01T10:00:00.000Z')
|
|
seedLog('l2', 'c1', '2026-09-03T10:00:00.000Z', 'news_angelegt')
|
|
seedLog('l3', 'c1', '2026-09-02T10:00:00.000Z', 'beendet')
|
|
seedLog('l4', 'c2', '2026-09-04T10:00:00.000Z')
|
|
const list = await listViewLog('c1')
|
|
assert.deepEqual(list.map((e) => e.id), ['l2', 'l3', 'l1'])
|
|
assert.deepEqual(list[0], { id: 'l2', at: '2026-09-03T10:00:00.000Z', staffName: 'Andrej', action: 'news_angelegt', detail: '' })
|
|
})
|
|
|
|
test('purgeViewLog loescht nur Eintraege aelter als 365 Tage', async () => {
|
|
const now = new Date('2026-10-01T12:00:00.000Z')
|
|
seedLog('p1', 'c3', new Date(now - 366 * DAY_MS).toISOString())
|
|
seedLog('p2', 'c3', new Date(now - 364 * DAY_MS).toISOString())
|
|
seedLog('p3', 'c3', new Date(now - 1 * DAY_MS).toISOString())
|
|
const { deleted } = await purgeViewLog({ now })
|
|
assert.equal(deleted, 1)
|
|
assert.deepEqual(entries().filter((d) => d.customerId === 'c3').map((d) => d.$id).sort(), ['p2', 'p3'])
|
|
})
|
|
|
|
test('purgeViewLog arbeitet seitenweise (250 alte Eintraege -> deleted 250)', async () => {
|
|
const now = new Date('2026-10-01T12:00:00.000Z')
|
|
const old = new Date(now - 400 * DAY_MS).toISOString()
|
|
for (let i = 0; i < 250; i += 1) seedLog(`old${i}`, 'c4', old)
|
|
seedLog('keep', 'c4', now.toISOString())
|
|
const { deleted } = await purgeViewLog({ now })
|
|
assert.equal(deleted, 250)
|
|
assert.deepEqual(entries().filter((d) => d.customerId === 'c4').map((d) => d.$id), ['keep'])
|
|
})
|