269 lines
14 KiB
JavaScript
269 lines
14 KiB
JavaScript
import { test, after } 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'
|
||
import { SHOP_DATETIMES } from '../server/services/shop/schema.js'
|
||
|
||
// Einstellungen des Shops: reines Normalisieren (Fehler je Feld) und Speichern mit Versionspruefung gegen den Appwrite-Nachbau.
|
||
const aw = await startAppwriteMock({ schema: { shopConfig: schemaKeys('shopConfig') }, datetimes: SHOP_DATETIMES })
|
||
after(() => aw.close())
|
||
useTestEnv({ appwriteUrl: aw.url, stripeUrl: 'http://127.0.0.1:1' })
|
||
|
||
const { config } = await import('../server/config.js')
|
||
const { createDocument, getDocument } = await import('../server/services/appwriteAdmin.js')
|
||
const E = await import('../server/services/shop/einstellungen.js')
|
||
const { STANDARD_EINSTELLUNGEN, normalisiereEinstellungen, ladeEinstellungen, speichereEinstellungen, nachSpeichern, istVollstaendig } = E
|
||
|
||
const komplett = (over = {}) => ({
|
||
shopname: 'Seifenmanufaktur Mustermann',
|
||
anbieter: {
|
||
name: 'Erika Mustermann',
|
||
strasse: 'Musterstraße 1',
|
||
plz: '80331',
|
||
ort: 'München',
|
||
land: 'DE',
|
||
email: 'Erika@Example.com',
|
||
telefon: '089 123456',
|
||
ustId: '',
|
||
},
|
||
kleinunternehmer: true,
|
||
versand: { aktiv: true, laender: ['DE'], kostenCent: { DE: 490 }, freiAbCent: null },
|
||
abholung: { aktiv: false, adresse: '', hinweis: '' },
|
||
lieferzeit: '2–4 Werktage',
|
||
zahlungsarten: ['card'],
|
||
akzent: '#111111',
|
||
mindestbestellCent: null,
|
||
ruecksendekosten: 'kaeufer',
|
||
...over,
|
||
})
|
||
const fehler = (eingabe) => normalisiereEinstellungen(eingabe).fehler
|
||
const ok = (eingabe) => {
|
||
const r = normalisiereEinstellungen(eingabe)
|
||
assert.equal(r.fehler, undefined, JSON.stringify(r.fehler))
|
||
return r.einstellungen
|
||
}
|
||
|
||
test('Standardwerte sind eingefroren, so wie im Plan und selbst gueltig', () => {
|
||
assert.ok(Object.isFrozen(STANDARD_EINSTELLUNGEN))
|
||
assert.ok(Object.isFrozen(STANDARD_EINSTELLUNGEN.versand))
|
||
assert.ok(Object.isFrozen(STANDARD_EINSTELLUNGEN.anbieter))
|
||
assert.deepEqual(STANDARD_EINSTELLUNGEN, {
|
||
shopname: '',
|
||
anbieter: { name: '', strasse: '', plz: '', ort: '', land: 'DE', email: '', telefon: '', ustId: '' },
|
||
kleinunternehmer: true,
|
||
versand: { aktiv: true, laender: ['DE'], kostenCent: { DE: 490 }, freiAbCent: null },
|
||
abholung: { aktiv: false, adresse: '', hinweis: '' },
|
||
lieferzeit: '2–4 Werktage',
|
||
zahlungsarten: ['card'],
|
||
akzent: '#111111',
|
||
mindestbestellCent: null,
|
||
ruecksendekosten: 'kaeufer',
|
||
})
|
||
assert.deepEqual(ok(STANDARD_EINSTELLUNGEN), STANDARD_EINSTELLUNGEN)
|
||
assert.equal(istVollstaendig(STANDARD_EINSTELLUNGEN), false)
|
||
})
|
||
|
||
test('komplette Eingabe: normalisiert, E-Mail klein, ohne gemeinsame Referenzen', () => {
|
||
const eingabe = komplett()
|
||
const e = ok(eingabe)
|
||
assert.equal(e.anbieter.email, 'erika@example.com')
|
||
assert.notEqual(e.versand, eingabe.versand)
|
||
assert.notEqual(e.versand.laender, eingabe.versand.laender)
|
||
assert.notEqual(e.versand.kostenCent, eingabe.versand.kostenCent)
|
||
assert.notEqual(e.anbieter, eingabe.anbieter)
|
||
assert.notEqual(e.zahlungsarten, eingabe.zahlungsarten)
|
||
e.versand.laender.push('X')
|
||
assert.deepEqual(eingabe.versand.laender, ['DE'])
|
||
assert.deepEqual(STANDARD_EINSTELLUNGEN.versand.laender, ['DE'])
|
||
assert.equal(istVollstaendig(komplett()), true)
|
||
})
|
||
|
||
test('leere und fehlende Eingabe: Standardwerte; keine Objekte: Fehler', () => {
|
||
assert.deepEqual(ok({}), STANDARD_EINSTELLUNGEN)
|
||
for (const x of [null, undefined, 'x', 5, [], true]) assert.ok(fehler(x)?.einstellungen, String(x))
|
||
})
|
||
|
||
test('unbekannte Schluessel werden ignoriert, auch __proto__', () => {
|
||
const e = ok(JSON.parse('{"foo":1,"__proto__":{"x":1},"anbieter":{"zusatz":"a"},"versand":{"y":2}}'))
|
||
assert.deepEqual(Object.keys(e).sort(), Object.keys(STANDARD_EINSTELLUNGEN).sort())
|
||
assert.equal(e.anbieter.zusatz, undefined)
|
||
assert.equal(e.versand.y, undefined)
|
||
assert.equal({}.x, undefined)
|
||
})
|
||
|
||
test('Texte: getrimmt, Steuerzeichen entfernt, Laengengrenzen je Feld', () => {
|
||
const e = ok(komplett({ shopname: ' Laden\u0000 Eins \n', abholung: { aktiv: true, adresse: 'Weg 1\r\n80331 München\u0007', hinweis: 'Klingeln' } }))
|
||
assert.equal(e.shopname, 'Laden Eins')
|
||
assert.equal(e.abholung.adresse, 'Weg 1\n80331 München')
|
||
const grenzen = [
|
||
['shopname', komplett({ shopname: 'a'.repeat(81) })],
|
||
['anbieter.name', komplett({ anbieter: { ...komplett().anbieter, name: 'a'.repeat(121) } })],
|
||
['anbieter.strasse', komplett({ anbieter: { ...komplett().anbieter, strasse: 'a'.repeat(121) } })],
|
||
['anbieter.ort', komplett({ anbieter: { ...komplett().anbieter, ort: 'a'.repeat(81) } })],
|
||
['anbieter.telefon', komplett({ anbieter: { ...komplett().anbieter, telefon: '1'.repeat(41) } })],
|
||
['lieferzeit', komplett({ lieferzeit: 'a'.repeat(81) })],
|
||
['abholung.hinweis', komplett({ abholung: { aktiv: true, adresse: 'Weg 1', hinweis: 'a'.repeat(301) } })],
|
||
['abholung.adresse', komplett({ abholung: { aktiv: true, adresse: 'a'.repeat(301), hinweis: '' } })],
|
||
]
|
||
for (const [pfad, eingabe] of grenzen) assert.ok(fehler(eingabe)?.[pfad], pfad)
|
||
ok(komplett({ shopname: 'a'.repeat(80), lieferzeit: 'abc' }))
|
||
assert.ok(fehler(komplett({ lieferzeit: 'ab' }))?.lieferzeit)
|
||
assert.ok(fehler(komplett({ shopname: 5 }))?.shopname)
|
||
})
|
||
|
||
test('Anbieter: E-Mail, PLZ je Land, Land, USt-IdNr.', () => {
|
||
const an = (over) => komplett({ anbieter: { ...komplett().anbieter, ...over } })
|
||
for (const mail of ['x', 'a@b', 'a b@c.de', 'a@b.de, c@d.de', `${'a'.repeat(250)}@b.de`, 'a@@b.de', '<a@b.de>']) {
|
||
assert.ok(fehler(an({ email: mail }))?.['anbieter.email'], mail)
|
||
}
|
||
assert.equal(ok(an({ email: ' A@B.de ' })).anbieter.email, 'a@b.de')
|
||
assert.ok(fehler(an({ plz: '8033' }))?.['anbieter.plz'])
|
||
assert.ok(fehler(an({ plz: '80a31' }))?.['anbieter.plz'])
|
||
assert.equal(ok(an({ land: 'AT', plz: '1010' })).anbieter.land, 'AT')
|
||
assert.ok(fehler(an({ land: 'AT', plz: '80331' }))?.['anbieter.plz'])
|
||
assert.ok(fehler(an({ land: 'FR' }))?.['anbieter.land'])
|
||
assert.equal(ok(an({ ustId: ' de123456789 ' })).anbieter.ustId, 'DE123456789')
|
||
assert.equal(ok(an({ ustId: 'ATU12345678' })).anbieter.ustId, 'ATU12345678')
|
||
for (const u of ['DE12345678', 'FR123456789', 'ATU1234567', 'DE1234567890']) assert.ok(fehler(an({ ustId: u }))?.['anbieter.ustId'], u)
|
||
})
|
||
|
||
test('Versand: Laender, Kosten je Land, Freigrenze', () => {
|
||
const vs = (over) => komplett({ versand: { ...komplett().versand, ...over } })
|
||
const at = ok(vs({ laender: ['AT', 'DE'], kostenCent: { DE: 490, AT: 990, FR: 5 } }))
|
||
assert.deepEqual(at.versand.laender, ['DE', 'AT'])
|
||
assert.deepEqual(at.versand.kostenCent, { DE: 490, AT: 990 })
|
||
assert.ok(fehler(vs({ laender: ['DE', 'AT'], kostenCent: { DE: 490 } }))?.['versand.kostenCent.AT'])
|
||
assert.ok(fehler(vs({ kostenCent: { DE: 4.9 } }))?.['versand.kostenCent.DE'])
|
||
assert.ok(fehler(vs({ kostenCent: { DE: '490' } }))?.['versand.kostenCent.DE'])
|
||
assert.ok(fehler(vs({ kostenCent: { DE: -1 } }))?.['versand.kostenCent.DE'])
|
||
assert.ok(fehler(vs({ kostenCent: { DE: 100001 } }))?.['versand.kostenCent.DE'])
|
||
assert.equal(ok(vs({ kostenCent: { DE: 0 } })).versand.kostenCent.DE, 0)
|
||
assert.equal(ok(vs({ kostenCent: { DE: 100000 } })).versand.kostenCent.DE, 100000)
|
||
for (const l of [['AT'], ['DE', 'FR'], [], 'DE', ['DE', 'DE', 'AT', 'CH']]) assert.ok(fehler(vs({ laender: l }))?.['versand.laender'], JSON.stringify(l))
|
||
assert.equal(ok(vs({ freiAbCent: 5000 })).versand.freiAbCent, 5000)
|
||
assert.equal(ok(vs({ freiAbCent: null })).versand.freiAbCent, null)
|
||
for (const f of [0, -5, 12.5, '5000', Infinity]) assert.ok(fehler(vs({ freiAbCent: f }))?.['versand.freiAbCent'], String(f))
|
||
assert.ok(fehler(vs({ aktiv: 'ja' }))?.['versand.aktiv'])
|
||
})
|
||
|
||
test('mindestens Versand oder Abholung; Abholadresse Pflicht', () => {
|
||
const nichts = fehler(komplett({ versand: { ...komplett().versand, aktiv: false } }))
|
||
assert.ok(nichts?.['versand.aktiv'])
|
||
const abholungNur = ok(komplett({ versand: { ...komplett().versand, aktiv: false }, abholung: { aktiv: true, adresse: 'Lager 3', hinweis: 'Mo–Fr' } }))
|
||
assert.equal(abholungNur.abholung.adresse, 'Lager 3')
|
||
assert.ok(fehler(komplett({ abholung: { aktiv: true, adresse: ' ', hinweis: '' } }))?.['abholung.adresse'])
|
||
// inaktive Abholung braucht keine Adresse
|
||
ok(komplett({ abholung: { aktiv: false, adresse: '', hinweis: '' } }))
|
||
})
|
||
|
||
test('Zahlungsarten: card immer dabei, nur bekannte, feste Reihenfolge', () => {
|
||
assert.deepEqual(ok(komplett({ zahlungsarten: ['sepa_debit', 'klarna'] })).zahlungsarten, ['card', 'klarna', 'sepa_debit'])
|
||
assert.deepEqual(ok(komplett({ zahlungsarten: ['card', 'card'] })).zahlungsarten, ['card'])
|
||
assert.deepEqual(ok(komplett({ zahlungsarten: [] })).zahlungsarten, ['card'])
|
||
assert.ok(fehler(komplett({ zahlungsarten: ['card', 'paypal'] }))?.zahlungsarten)
|
||
assert.ok(fehler(komplett({ zahlungsarten: 'card' }))?.zahlungsarten)
|
||
})
|
||
|
||
test('Akzent, Mindestbestellwert, Ruecksendekosten, Kleinunternehmer', () => {
|
||
assert.equal(ok(komplett({ akzent: '#AABBCC' })).akzent, '#aabbcc')
|
||
for (const a of ['AABBCC', '#abc', '#12345g', 'red', '#1234567']) assert.ok(fehler(komplett({ akzent: a }))?.akzent, a)
|
||
assert.equal(ok(komplett({ mindestbestellCent: 1000 })).mindestbestellCent, 1000)
|
||
for (const m of [0, -1, 1.5, '10']) assert.ok(fehler(komplett({ mindestbestellCent: m }))?.mindestbestellCent, String(m))
|
||
assert.equal(ok(komplett({ ruecksendekosten: 'haendler' })).ruecksendekosten, 'haendler')
|
||
assert.ok(fehler(komplett({ ruecksendekosten: 'egal' }))?.ruecksendekosten)
|
||
assert.equal(ok(komplett({ kleinunternehmer: false })).kleinunternehmer, false)
|
||
assert.ok(fehler(komplett({ kleinunternehmer: 'nein' }))?.kleinunternehmer)
|
||
})
|
||
|
||
test('mehrere Fehler zugleich, alle Texte in Du-Form ohne Sie', () => {
|
||
const f = fehler(komplett({ akzent: 'x', lieferzeit: 'a', zahlungsarten: ['paypal'], anbieter: { ...komplett().anbieter, email: 'x', plz: '1' } }))
|
||
assert.deepEqual(Object.keys(f).sort(), ['akzent', 'anbieter.email', 'anbieter.plz', 'lieferzeit', 'zahlungsarten'])
|
||
for (const t of Object.values(f)) {
|
||
assert.equal(typeof t, 'string')
|
||
assert.doesNotMatch(t, /\bSie\b|\bIhre?[mnrs]?\b/)
|
||
}
|
||
})
|
||
|
||
test('istVollstaendig: Pflichtangaben und gueltige Normalisierung', () => {
|
||
assert.equal(istVollstaendig(komplett()), true)
|
||
for (const pfad of ['shopname', 'lieferzeit']) assert.equal(istVollstaendig(komplett({ [pfad]: '' })), false, pfad)
|
||
for (const feld of ['name', 'strasse', 'plz', 'ort', 'email']) {
|
||
assert.equal(istVollstaendig(komplett({ anbieter: { ...komplett().anbieter, [feld]: '' } })), false, feld)
|
||
}
|
||
assert.equal(istVollstaendig(komplett({ anbieter: { ...komplett().anbieter, telefon: '', ustId: '' } })), true)
|
||
assert.equal(istVollstaendig(komplett({ akzent: 'kaputt' })), false)
|
||
assert.equal(istVollstaendig(null), false)
|
||
})
|
||
|
||
test('ladeEinstellungen: kaputt oder fehlend ergibt Standard, Teilwerte werden aufgefuellt', () => {
|
||
for (const doc of [null, undefined, {}, { einstellungen: '' }, { einstellungen: '{kaputt' }, { einstellungen: '[1]' }, { einstellungen: 'null' }, { einstellungen: '"x"' }]) {
|
||
assert.deepEqual(ladeEinstellungen(doc), STANDARD_EINSTELLUNGEN)
|
||
}
|
||
const teil = ladeEinstellungen({ einstellungen: JSON.stringify({ shopname: 'Laden', anbieter: { ort: 'Ulm' }, versand: { freiAbCent: 5000 } }) })
|
||
assert.equal(teil.shopname, 'Laden')
|
||
assert.equal(teil.anbieter.ort, 'Ulm')
|
||
assert.equal(teil.anbieter.land, 'DE')
|
||
assert.equal(teil.versand.freiAbCent, 5000)
|
||
assert.deepEqual(teil.versand.kostenCent, { DE: 490 })
|
||
assert.equal(teil.lieferzeit, '2–4 Werktage')
|
||
// gespeicherte Werte werden nicht in die Standardwerte geschrieben
|
||
teil.versand.laender.push('AT')
|
||
assert.deepEqual(STANDARD_EINSTELLUNGEN.versand.laender, ['DE'])
|
||
assert.notEqual(ladeEinstellungen(null), STANDARD_EINSTELLUNGEN)
|
||
})
|
||
|
||
test('speichereEinstellungen: Version steigt, Versionspruefung, Hooks', async () => {
|
||
const id = 'f_p_shop0001_shop'
|
||
await createDocument(config.collections.shopConfig, { customerId: 'c1', projectId: 'p1', siteKey: 'abcdefghijklmnop' }, id)
|
||
const gerufen = []
|
||
const abmelden = nachSpeichern((shopId) => gerufen.push(shopId))
|
||
nachSpeichern(() => {
|
||
throw new Error('Hook kaputt')
|
||
})
|
||
nachSpeichern(async () => {
|
||
throw new Error('Hook async kaputt')
|
||
})
|
||
|
||
const r1 = await speichereEinstellungen(id, komplett(), { erwarteteVersion: 0 })
|
||
assert.equal(r1.version, 1)
|
||
assert.equal(r1.einstellungen.anbieter.email, 'erika@example.com')
|
||
let doc = await getDocument(config.collections.shopConfig, id)
|
||
assert.equal(doc.version, 1)
|
||
assert.equal(JSON.parse(doc.einstellungen).shopname, 'Seifenmanufaktur Mustermann')
|
||
assert.deepEqual(ladeEinstellungen(doc), r1.einstellungen)
|
||
assert.deepEqual(gerufen, [id])
|
||
|
||
const r2 = await speichereEinstellungen(id, komplett({ shopname: 'Neu' }))
|
||
assert.equal(r2.version, 2)
|
||
await assert.rejects(speichereEinstellungen(id, komplett({ shopname: 'Alt' }), { erwarteteVersion: 1 }), (err) => {
|
||
assert.equal(err.status, 409)
|
||
assert.equal(err.message, 'Die Einstellungen wurden inzwischen geändert. Bitte lade die Seite neu.')
|
||
return true
|
||
})
|
||
doc = await getDocument(config.collections.shopConfig, id)
|
||
assert.equal(doc.version, 2)
|
||
assert.equal(JSON.parse(doc.einstellungen).shopname, 'Neu')
|
||
assert.equal(gerufen.length, 2)
|
||
|
||
// ungueltige Einstellungen werden nicht gespeichert: 400 mit Feldfehlern
|
||
await assert.rejects(speichereEinstellungen(id, komplett({ akzent: 'x' })), (err) => err.status === 400 && Boolean(err.fehler.akzent))
|
||
assert.equal((await getDocument(config.collections.shopConfig, id)).version, 2)
|
||
assert.equal(gerufen.length, 2)
|
||
|
||
// gleichzeitige Speicherungen mit derselben erwarteten Version: genau eine gewinnt
|
||
const rennen = await Promise.allSettled([
|
||
speichereEinstellungen(id, komplett({ shopname: 'A' }), { erwarteteVersion: 2 }),
|
||
speichereEinstellungen(id, komplett({ shopname: 'B' }), { erwarteteVersion: 2 }),
|
||
])
|
||
assert.equal(rennen.filter((x) => x.status === 'fulfilled').length, 1)
|
||
assert.equal((await getDocument(config.collections.shopConfig, id)).version, 3)
|
||
|
||
abmelden()
|
||
await speichereEinstellungen(id, komplett({ shopname: 'C' }))
|
||
assert.equal(gerufen.length, 3) // A oder B war der dritte Aufruf, danach keiner mehr
|
||
|
||
// unbekannter Shop
|
||
await assert.rejects(speichereEinstellungen('gibt-es-nicht', komplett()), (err) => err.status === 404)
|
||
})
|