Webshop A1/A2: Konfiguration, Schalter, Katalog shopSetup, Schema und Setup-Skript

config.shop (SHOP_ENABLED, SHOP_PILOT_EMAILS, SHOP_UPLOAD_DIR, STRIPE_CONNECT_WEBHOOK_SECRET), config.shopSetup (299 € einmalig),
Collection-IDs shopConfig/shopProducts/shopOrders/shopWiderrufe, services/shop/schalter.js, Katalogschluessel shopSetup in
stripeBilling und stripe-products.mjs, services/shop/schema.js, scripts/shop-setup.mjs (--check), schemaKeys fuer die Test-Mocks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Opus 5.5 committed 2026-10-05 08:30:21 +00:00
1 parent b168b10485
commit e9ab15bdb5
11 files changed
+759 -6

No files matched your search

+163
View File
@@ -0,0 +1,163 @@
import { test, beforeEach, afterEach } from 'node:test'
import assert from 'node:assert/strict'
import path from 'node:path'
import { config } from '../server/config.js'
import { shopFuerKunde, shopModus } from '../server/services/shop/schalter.js'
const SHOP_ENV = [
'SHOP_ENABLED',
'SHOP_PILOT_EMAILS',
'SHOP_UPLOAD_DIR',
'STRIPE_CONNECT_WEBHOOK_SECRET',
'APPWRITE_COLLECTION_SHOP_CONFIG',
'APPWRITE_COLLECTION_SHOP_PRODUCTS',
'APPWRITE_COLLECTION_SHOP_ORDERS',
'APPWRITE_COLLECTION_SHOP_WIDERRUFE',
]
let original
beforeEach(() => {
original = { ...config.shop }
})
afterEach(() => {
Object.assign(config.shop, original)
})
/** Frische config.js-Instanz mit genau diesen Shop-Umgebungsvariablen (alle anderen entfernt). */
let freshCounter = 0
async function freshConfig(env = {}) {
const saved = Object.fromEntries(SHOP_ENV.map((key) => [key, process.env[key]]))
for (const key of SHOP_ENV) delete process.env[key]
Object.assign(process.env, env)
try {
freshCounter += 1
return (await import(`../server/config.js?shop-test=${freshCounter}`)).config
} finally {
for (const key of SHOP_ENV) {
if (saved[key] === undefined) delete process.env[key]
else process.env[key] = saved[key]
}
}
}
test('Konfiguration: Vorgaben ohne Umgebungsvariablen', async () => {
const fresh = await freshConfig()
assert.deepEqual(fresh.shop, {
mode: 'off',
pilotEmails: [],
fassung: '2026-10-05',
uploadDir: path.join(process.cwd(), 'upload-data', 'shop'),
connectWebhookSecret: '',
bestellAblaufMinuten: 30,
maxArtikel: 500,
maxBilder: 4,
maxVarianten: 30,
anonymisierenJahre: 3,
loeschTageNachAbschalten: 90,
unbezahltLoeschTage: 30,
})
assert.equal(fresh.collections.shopConfig, 'shopConfig')
assert.equal(fresh.collections.shopProducts, 'shopProducts')
assert.equal(fresh.collections.shopOrders, 'shopOrders')
assert.equal(fresh.collections.shopWiderrufe, 'shopWiderrufe')
})
test('Konfiguration: Umgebungsvariablen werden gelesen und bereinigt', async () => {
const fresh = await freshConfig({
SHOP_ENABLED: ' Pilot ',
SHOP_PILOT_EMAILS: ' Kenso.Gri@Gmail.com, ,justin@webklar.com ',
SHOP_UPLOAD_DIR: '/tmp/shop-bilder',
STRIPE_CONNECT_WEBHOOK_SECRET: 'whsec_connect',
APPWRITE_COLLECTION_SHOP_CONFIG: 'sConfig',
APPWRITE_COLLECTION_SHOP_PRODUCTS: 'sProducts',
APPWRITE_COLLECTION_SHOP_ORDERS: 'sOrders',
APPWRITE_COLLECTION_SHOP_WIDERRUFE: 'sWiderrufe',
})
assert.equal(fresh.shop.mode, 'pilot')
assert.deepEqual(fresh.shop.pilotEmails, ['kenso.gri@gmail.com', 'justin@webklar.com'])
assert.equal(fresh.shop.uploadDir, '/tmp/shop-bilder')
assert.equal(fresh.shop.connectWebhookSecret, 'whsec_connect')
assert.deepEqual(
[fresh.collections.shopConfig, fresh.collections.shopProducts, fresh.collections.shopOrders, fresh.collections.shopWiderrufe],
['sConfig', 'sProducts', 'sOrders', 'sWiderrufe'],
)
})
test('Konfiguration: leerer oder unbekannter Schalter ist off', async () => {
for (const value of ['', ' ', 'banane', 'true', '1', 'ON ']) {
const fresh = await freshConfig({ SHOP_ENABLED: value })
assert.equal(fresh.shop.mode, value.trim().toLowerCase() === 'on' ? 'on' : 'off', `SHOP_ENABLED=${JSON.stringify(value)}`)
}
})
test('Konfiguration: Katalogeintrag shopSetup', () => {
assert.deepEqual(config.shopSetup, {
key: 'shopSetup',
name: 'Webshop – Einrichtung',
amountCents: 29900,
lookupKey: 'webklar_shop_einrichtung_29900',
oneTime: true,
priceNickname: 'Webshop-Einrichtung 299 €',
})
})
test('shopModus: leer und unbekannt sind off, pilot und on bleiben', () => {
for (const value of [undefined, null, '', 'banane', 'PILOT', 'true']) {
config.shop.mode = value
assert.equal(shopModus(), 'off', `mode=${JSON.stringify(value)}`)
}
config.shop.mode = 'pilot'
assert.equal(shopModus(), 'pilot')
config.shop.mode = 'on'
assert.equal(shopModus(), 'on')
})
test('shopModus: Aenderung von config.shop.mode wirkt sofort', () => {
config.shop.mode = 'off'
assert.equal(shopModus(), 'off')
config.shop.mode = 'on'
assert.equal(shopModus(), 'on')
config.shop.mode = 'off'
assert.equal(shopModus(), 'off')
})
test('shopFuerKunde: off sperrt alle, auch mit Stern in der Liste', () => {
config.shop.mode = 'off'
config.shop.pilotEmails = ['*', 'kenso.gri@gmail.com']
assert.equal(shopFuerKunde('kenso.gri@gmail.com'), false)
assert.equal(shopFuerKunde('jemand@example.com'), false)
})
test('shopFuerKunde: pilot erlaubt nur Adressen der Liste, Schreibweise egal', () => {
config.shop.mode = 'pilot'
config.shop.pilotEmails = ['kenso.gri@gmail.com']
assert.equal(shopFuerKunde('kenso.gri@gmail.com'), true)
assert.equal(shopFuerKunde(' Kenso.Gri@Gmail.com '), true)
assert.equal(shopFuerKunde('jemand@example.com'), false)
assert.equal(shopFuerKunde(''), false)
assert.equal(shopFuerKunde(undefined), false)
assert.equal(shopFuerKunde(null), false)
})
test('shopFuerKunde: pilot mit leerer Liste laesst niemanden durch, Stern alle (auch ohne Adresse)', () => {
config.shop.mode = 'pilot'
config.shop.pilotEmails = []
assert.equal(shopFuerKunde('kenso.gri@gmail.com'), false)
assert.equal(shopFuerKunde(''), false)
config.shop.pilotEmails = ['*']
assert.equal(shopFuerKunde('jemand@example.com'), true)
assert.equal(shopFuerKunde(''), true)
})
test('shopFuerKunde: on erlaubt alle Kunden, die Liste spielt keine Rolle', () => {
config.shop.mode = 'on'
config.shop.pilotEmails = []
assert.equal(shopFuerKunde('jemand@example.com'), true)
assert.equal(shopFuerKunde(''), true)
})
test('shopFuerKunde: Unbekannter Modus gilt als off', () => {
config.shop.mode = 'banane'
config.shop.pilotEmails = ['*']
assert.equal(shopFuerKunde('kenso.gri@gmail.com'), false)
})