Shop D2: Kundenrouten Stripe-Konto (einrichten, Link, aktualisieren) mit Sperre gegen Doppelklick
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
98d1e6613d
commit
38bd56fe8b
2 files changed
+319
-2
No files matched your search
+74
-2
@@ -8,6 +8,7 @@ import { isKundenansicht, logIfKundenansicht } from '../services/kundenansicht.j
|
||||
import { createWriteLimiter } from '../services/writeLimiter.js'
|
||||
import { anfrageAnsicht, ensureShopConfig, leseShopConfig, stelleAnfrage, zieheZurueck } from '../services/shop/anfrage.js'
|
||||
import { shopFuerKunde } from '../services/shop/schalter.js'
|
||||
import { kontoAnlegen, kontoStatus, onboardingLink, statusAusKonto } from '../services/shop/connect.js'
|
||||
import { uploadSlot } from './chat.js'
|
||||
import { istVollstaendig, speichereEinstellungen } from '../services/shop/einstellungen.js'
|
||||
import { bereitschaft, istGeoeffnet, ladeShop, leereCache } from '../services/shop/kontext.js'
|
||||
@@ -24,7 +25,7 @@ import {
|
||||
|
||||
/**
|
||||
* Kunden-Schnittstelle des Webshops: /api/shop (Spezifikation 2026-10-05-webshop-design.md). Stand: Anfrage (B1) und
|
||||
* Verwaltung (C7): Status, Einstellungen, Artikel, Rechtstexte, Oeffnen, Bilder. Stripe (D2) und Bestellungen (E) kommen dazu.
|
||||
* Verwaltung (C7): Status, Einstellungen, Artikel, Rechtstexte, Oeffnen, Bilder. Stripe-Konto (D2) und Bestellungen (E) kommen dazu.
|
||||
*
|
||||
* Jede Route: Session, projectId (Query oder Body), eigenes Projekt. Die Verwaltungs-Routen laufen ueber mitShop: Schalter
|
||||
* (409 nicht verfuegbar), Funktion im Zustand setup|active (lesend auch disabled), shopConfig vorhanden (setup|active legt es an).
|
||||
@@ -50,11 +51,14 @@ const MELDUNG = {
|
||||
zuVieleBilder: 'Zu viele Bilder in kurzer Zeit. Bitte versuch es später wieder.',
|
||||
bildFehlt: 'Bild nicht gefunden.',
|
||||
eingabe: 'Die Eingabe fehlt oder ist unlesbar.',
|
||||
stripeKundenansicht: 'In der Kundenansicht kann das Stripe-Konto nicht eingerichtet werden.',
|
||||
stripeFern: 'Stripe ist gerade nicht erreichbar. Bitte versuch es gleich noch einmal.',
|
||||
keinKonto: 'Es ist noch kein Stripe-Konto angelegt.',
|
||||
}
|
||||
|
||||
/** Fehler des Kunden (4xx/503 ohne Appwrite-Typ) wie sie sind, alles andere 500 ohne Rohtext. */
|
||||
function fail(res, err) {
|
||||
const own = ((err?.status >= 400 && err.status < 500) || err?.status === 503) && !err.type
|
||||
const own = ((err?.status >= 400 && err.status < 500) || err?.status === 502 || err?.status === 503) && !err.type
|
||||
if (!own) {
|
||||
console.error(`[shop] ${err?.name ?? 'Fehler'} ${err?.status ?? ''} ${String(err?.message ?? '').slice(0, 200)}`.trim())
|
||||
return res.status(500).json({ error: 'Interner Serverfehler' })
|
||||
@@ -449,6 +453,74 @@ router.post('/oeffnen', requireSession, nurKunde, limitWrites, route(async (req,
|
||||
res.json({ schalter: req.body.offen, geoeffnet: await istGeoeffnet({ doc, shop: neu, project: null }, { produkte }), bereitschaft: await bereitschaft({ doc, shop: neu, project: null }, { produkte }) })
|
||||
}))
|
||||
|
||||
// ---- Stripe-Konto (D2)
|
||||
|
||||
/** Nur der Kunde selbst richtet sein Stripe-Konto ein (weder Kundenansicht noch "Ansehen als"). */
|
||||
const nurKundeStripe = (req, res, next) => {
|
||||
if (isKundenansicht(req.session) || req.session?.viewAs) return res.status(403).json({ error: MELDUNG.stripeKundenansicht })
|
||||
return next()
|
||||
}
|
||||
|
||||
/** Stripe-Aufruf: jeder Fehler von Stripe wird zu 502 mit festem Text (Stripe-Texte verlassen den Server nie, geloggt wird nur Typ und Code). */
|
||||
async function ueberStripe(fn) {
|
||||
try {
|
||||
return await fn()
|
||||
} catch (err) {
|
||||
if (err?.status >= 400 && err.status < 500 && !err.type) throw err
|
||||
console.error(`[shop] Stripe ${String(err?.type ?? err?.name ?? 'Fehler').slice(0, 60)} ${String(err?.code ?? '').slice(0, 60)}`.trim())
|
||||
throw stop(502, MELDUNG.stripeFern)
|
||||
}
|
||||
}
|
||||
|
||||
const speichereStripe = async (shop, felder) => {
|
||||
await updateDocument(config.collections.shopConfig, shop.id, felder)
|
||||
leereCache(shop.siteKey)
|
||||
}
|
||||
|
||||
const stripeRueckUrl = (projectId, was) => `${config.publicBaseUrl}/dashboard.html#webshop?stripe=${was}&projekt=${encodeURIComponent(projectId)}`
|
||||
const onboardingFuer = (accountId, projectId) =>
|
||||
ueberStripe(() => onboardingLink(accountId, { returnUrl: stripeRueckUrl(projectId, 'zurueck'), refreshUrl: stripeRueckUrl(projectId, 'neu') }))
|
||||
|
||||
// Konto anlegen (einmalig, unter Sperre) und Onboarding-Link liefern
|
||||
router.post('/stripe/einrichten', requireSession, nurKundeStripe, limitWrites, route(async (req, res) => {
|
||||
const { shop, customerId, projectId, project } = await mitShop(req, { schreiben: true, zustaende: ['setup', 'active'] })
|
||||
const accountId = await withLocks([`shop-stripe:${shop.id}`], async () => {
|
||||
const frisch = await frischerShop(shop.id)
|
||||
if (frisch.stripeAccountId) return frisch.stripeAccountId
|
||||
const customer = await getDocument(config.collections.customers, customerId).catch(() => null)
|
||||
const neu = await ueberStripe(() => kontoAnlegen({ customer: customer || { $id: customerId, email: req.session?.email }, project }))
|
||||
let status
|
||||
try {
|
||||
status = await ueberStripe(() => kontoStatus(neu))
|
||||
} catch {
|
||||
status = statusAusKonto({})
|
||||
}
|
||||
await speichereStripe(shop, { stripeAccountId: neu, stripeStatus: JSON.stringify(status) })
|
||||
return neu
|
||||
})
|
||||
res.json({ url: await onboardingFuer(accountId, projectId) })
|
||||
}))
|
||||
|
||||
// Neuer Onboarding-Link (der alte laeuft nach wenigen Minuten ab)
|
||||
router.post('/stripe/link', requireSession, nurKundeStripe, limitWrites, route(async (req, res) => {
|
||||
const { shop, projectId } = await mitShop(req, { schreiben: true, zustaende: ['setup', 'active'] })
|
||||
if (!shop.stripeAccountId) throw stop(409, MELDUNG.keinKonto, 'kein_konto')
|
||||
res.json({ url: await onboardingFuer(shop.stripeAccountId, projectId) })
|
||||
}))
|
||||
|
||||
// Status bei Stripe neu lesen und speichern
|
||||
router.post('/stripe/aktualisieren', requireSession, nurKundeStripe, limitWrites, route(async (req, res) => {
|
||||
const { shop } = await mitShop(req, { schreiben: true, zustaende: ['setup', 'active'] })
|
||||
if (!shop.stripeAccountId) throw stop(409, MELDUNG.keinKonto, 'kein_konto')
|
||||
const status = await ueberStripe(() => kontoStatus(shop.stripeAccountId))
|
||||
const alt = shop.stripeStatus
|
||||
const zahlungsarten = Array.isArray(shop.einstellungen?.zahlungsarten) ? shop.einstellungen.zahlungsarten : ['card']
|
||||
const behalteHinweis = typeof alt?.hinweis === 'string' && alt.hinweis !== '' && status.charges_enabled === true && zahlungsarten.some((z) => z === 'klarna' || z === 'sepa')
|
||||
const neu = behalteHinweis ? { ...status, hinweis: alt.hinweis } : status
|
||||
await speichereStripe(shop, { stripeStatus: JSON.stringify(neu) })
|
||||
res.json({ stripe: stripeAnsicht({ ...shop, stripeStatus: neu }) })
|
||||
}))
|
||||
|
||||
// ---- Bilder
|
||||
|
||||
// Bild als rohe Bytes (application/octet-stream), hoechstens 8 MB; die Pruefung des Shops laeuft vor dem Einlesen
|
||||
|
||||
@@ -0,0 +1,245 @@
|
||||
import { test, after, beforeEach } 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'
|
||||
import { SHOP_DATETIMES } from '../server/services/shop/schema.js'
|
||||
|
||||
// Webshop D2: Stripe-Konto einrichten, neuer Onboarding-Link, Status aktualisieren (Fake-Stripe)
|
||||
const aw = await startAppwriteMock({
|
||||
schema: {
|
||||
portalFeatures: schemaKeys('portalFeatures'),
|
||||
shopConfig: schemaKeys('shopConfig'),
|
||||
shopProducts: schemaKeys('shopProducts'),
|
||||
customerViewLog: schemaKeys('customerViewLog'),
|
||||
},
|
||||
datetimes: SHOP_DATETIMES,
|
||||
})
|
||||
const st = await startFakeStripe()
|
||||
useTestEnv({ appwriteUrl: aw.url, stripeUrl: st.url })
|
||||
const { config } = await import('../server/config.js')
|
||||
const { startTestApp, send } = await import('./support/testApp.js')
|
||||
const { customerCookie, kundenansichtCookie, viewAsCookie, seedFixtures, customers } = await import('./support/fixtures.js')
|
||||
const { default: shopRoutes, resetShopRoutesForTests } = await import('../server/routes/shop.js')
|
||||
const { kundenansichtGuard } = await import('../server/middleware/kundenansichtGuard.js')
|
||||
const sf = await import('../server/services/siteFeatures.js')
|
||||
const { deleteDocument, updateDocument } = await import('../server/services/appwriteAdmin.js')
|
||||
const { leereCache } = await import('../server/services/shop/kontext.js')
|
||||
|
||||
const app = await startTestApp({ '/': kundenansichtGuard, '/api/shop': shopRoutes })
|
||||
after(() => Promise.all([app.close(), aw.close(), st.close()]))
|
||||
|
||||
seedFixtures(aw)
|
||||
aw.seed('websiteProjects', { $id: 'pt2', customerId: 'c1', projectName: 'Zweiter Shop' })
|
||||
aw.seed('websiteProjects', { $id: 'pt3', customerId: 'c2', projectName: 'Fremdes Projekt' })
|
||||
|
||||
// Je Test ein frisches Projekt: Kontoanlage ist je Projekt idempotent (Idempotency-Key), alte Konten wuerden sonst wiederkehren
|
||||
let P = 'pt1'
|
||||
let zaehler = 0
|
||||
const cookie = customerCookie('c1')
|
||||
const GEDULD = 'Stripe ist gerade nicht erreichbar. Bitte versuch es gleich noch einmal.'
|
||||
const post = (pfad, body = { projectId: P }, opts = { cookie }) => send('POST', `/api/shop/stripe/${pfad}`, { ...opts, body })
|
||||
const status = (projectId = P) => send('GET', `/api/shop/status?projectId=${projectId}`, { cookie })
|
||||
const plattform = () => st.calls.filter((c) => c.path === '/v1/accounts' || c.path === '/v1/account_links' || c.path.startsWith('/v1/accounts/'))
|
||||
|
||||
async function aktiviere(projectId = P, state = 'active') {
|
||||
const feature = await sf.ensureFeatureDoc('c1', projectId, 'shop')
|
||||
await updateDocument(config.collections.portalFeatures, feature.$id, sf.stateFields(state))
|
||||
return sf.getFeatureDoc('c1', projectId, 'shop')
|
||||
}
|
||||
const kontenFuer = (projectId) => [...st.accounts.values()].filter((a) => a.metadata?.projectId === projectId)
|
||||
const shopConfigDoc = (feature) => aw.docs('shopConfig').get(feature.$id)
|
||||
|
||||
beforeEach(async () => {
|
||||
P = `pn${(zaehler += 1)}`
|
||||
aw.seed('websiteProjects', { $id: P, customerId: 'c1', projectName: 'Töpferei', liveDomain: 'toepferei.example', archived: false })
|
||||
config.shop.mode = 'pilot'
|
||||
config.shop.pilotEmails = [customers.c1.email]
|
||||
resetShopRoutesForTests()
|
||||
for (const col of ['portalFeatures', 'shopConfig', 'shopProducts', 'customerViewLog']) {
|
||||
for (const id of [...aw.docs(col).keys()]) await deleteDocument(config.collections[col], id)
|
||||
}
|
||||
st.calls.length = 0
|
||||
leereCache()
|
||||
})
|
||||
|
||||
test('einrichten: legt ein Konto an, speichert Konto-ID und Status, liefert den Onboarding-Link mit Rücksprung-Adressen', async () => {
|
||||
const feature = await aktiviere()
|
||||
const res = await post('einrichten')
|
||||
assert.equal(res.status, 200)
|
||||
assert.match(res.body.url, /^https:\/\/connect\.stripe\.test\/setup\/acct_/)
|
||||
assert.deepEqual(Object.keys(res.body), ['url'])
|
||||
const doc = shopConfigDoc(feature)
|
||||
assert.match(doc.stripeAccountId, /^acct_/)
|
||||
assert.equal(kontenFuer(P).length, 1)
|
||||
const gespeichert = JSON.parse(doc.stripeStatus)
|
||||
assert.equal(gespeichert.charges_enabled, false)
|
||||
assert.deepEqual(gespeichert.currently_due, ['external_account'])
|
||||
// Plattform-Aufrufe ohne Stripe-Account-Kopfzeile, Konto mit Kunden-E-Mail und Projekt in den Metadaten
|
||||
assert.ok(plattform().length >= 3)
|
||||
for (const c of plattform()) assert.equal(c.account, undefined, `${c.method} ${c.path} darf keine Stripe-Account-Kopfzeile tragen`)
|
||||
const anlage = st.calls.find((c) => c.method === 'POST' && c.path === '/v1/accounts')
|
||||
assert.equal(anlage.body.email, customers.c1.email)
|
||||
assert.equal(anlage.body['metadata[projectId]'], P)
|
||||
const link = st.calls.find((c) => c.path === '/v1/account_links')
|
||||
assert.equal(link.body.account, doc.stripeAccountId)
|
||||
assert.equal(link.body.return_url, `${config.publicBaseUrl}/dashboard.html#webshop?stripe=zurueck&projekt=${P}`)
|
||||
assert.equal(link.body.refresh_url, `${config.publicBaseUrl}/dashboard.html#webshop?stripe=neu&projekt=${P}`)
|
||||
// GET /status zeigt das Konto als verbunden, ohne die Konto-ID
|
||||
const s = (await status()).body
|
||||
assert.equal(s.stripe.verbunden, true)
|
||||
assert.equal(JSON.stringify(s).includes(doc.stripeAccountId), false)
|
||||
})
|
||||
|
||||
test('einrichten: zwei parallele Aufrufe (Doppelklick) legen nur ein Konto an', async () => {
|
||||
const feature = await aktiviere(P, 'setup')
|
||||
const [a, b] = await Promise.all([post('einrichten'), post('einrichten')])
|
||||
assert.deepEqual([a.status, b.status], [200, 200])
|
||||
assert.equal(st.calls.filter((c) => c.method === 'POST' && c.path === '/v1/accounts').length, 1)
|
||||
assert.equal(kontenFuer(P).length, 1)
|
||||
assert.equal(st.calls.filter((c) => c.path === '/v1/account_links').length, 2)
|
||||
assert.ok(a.body.url && b.body.url)
|
||||
// weiterer Aufruf: Konto bleibt dasselbe
|
||||
const id = shopConfigDoc(feature).stripeAccountId
|
||||
assert.equal((await post('einrichten')).status, 200)
|
||||
assert.equal(shopConfigDoc(feature).stripeAccountId, id)
|
||||
assert.equal(kontenFuer(P).length, 1)
|
||||
})
|
||||
|
||||
test('link: braucht ein Konto (409), liefert danach einen neuen Link mit den Rücksprung-Adressen', async () => {
|
||||
await aktiviere()
|
||||
const ohne = await post('link')
|
||||
assert.deepEqual([ohne.status, ohne.body.error, ohne.body.code], [409, 'Es ist noch kein Stripe-Konto angelegt.', 'kein_konto'])
|
||||
assert.equal((await post('einrichten')).status, 200)
|
||||
st.calls.length = 0
|
||||
const res = await post('link')
|
||||
assert.equal(res.status, 200)
|
||||
assert.match(res.body.url, /^https:\/\/connect\.stripe\.test\/setup\//)
|
||||
assert.equal(st.calls.length, 1)
|
||||
assert.equal(st.calls[0].account, undefined)
|
||||
assert.equal(st.calls[0].body.return_url, `${config.publicBaseUrl}/dashboard.html#webshop?stripe=zurueck&projekt=${P}`)
|
||||
assert.equal(kontenFuer(P).length, 1)
|
||||
})
|
||||
|
||||
test('aktualisieren: braucht ein Konto (409), liest den Status neu und speichert ihn', async () => {
|
||||
const feature = await aktiviere()
|
||||
assert.equal((await post('aktualisieren')).status, 409)
|
||||
await post('einrichten')
|
||||
const id = shopConfigDoc(feature).stripeAccountId
|
||||
st.setAccount(id, { charges_enabled: true, payouts_enabled: true, details_submitted: true, requirements: { currently_due: [] } })
|
||||
const res = await post('aktualisieren')
|
||||
assert.equal(res.status, 200)
|
||||
assert.equal(res.body.stripe.verbunden, true)
|
||||
assert.equal(res.body.stripe.status.charges_enabled, true)
|
||||
assert.deepEqual(res.body.stripe.status.currently_due, [])
|
||||
assert.equal(JSON.parse(shopConfigDoc(feature).stripeStatus).charges_enabled, true)
|
||||
assert.equal(JSON.stringify(res.body).includes(id), false)
|
||||
// gleiche Form wie GET /status
|
||||
assert.deepEqual((await status()).body.stripe, res.body.stripe)
|
||||
})
|
||||
|
||||
test('aktualisieren: Hinweis zu Klarna/SEPA bleibt nur, solange Zahlungen möglich sind und eine der Zahlungsarten gewählt ist', async () => {
|
||||
const feature = await aktiviere()
|
||||
await post('einrichten')
|
||||
const id = shopConfigDoc(feature).stripeAccountId
|
||||
const setze = async (stripeStatus, zahlungsarten) => {
|
||||
const e = { zahlungsarten }
|
||||
await updateDocument(config.collections.shopConfig, feature.$id, { stripeStatus: JSON.stringify(stripeStatus), einstellungen: JSON.stringify(e) })
|
||||
leereCache()
|
||||
}
|
||||
st.setAccount(id, { charges_enabled: true, details_submitted: true, requirements: { currently_due: [] } })
|
||||
await setze({ charges_enabled: true, hinweis: 'Klarna ist noch nicht freigeschaltet.' }, ['card', 'klarna'])
|
||||
await post('aktualisieren')
|
||||
assert.equal(JSON.parse(shopConfigDoc(feature).stripeStatus).hinweis, 'Klarna ist noch nicht freigeschaltet.')
|
||||
await setze({ charges_enabled: true, hinweis: 'Klarna ist noch nicht freigeschaltet.' }, ['card'])
|
||||
await post('aktualisieren')
|
||||
assert.equal(JSON.parse(shopConfigDoc(feature).stripeStatus).hinweis, undefined)
|
||||
st.setAccount(id, { charges_enabled: false })
|
||||
await setze({ charges_enabled: true, hinweis: 'Klarna ist noch nicht freigeschaltet.' }, ['card', 'sepa'])
|
||||
await post('aktualisieren')
|
||||
assert.equal(JSON.parse(shopConfigDoc(feature).stripeStatus).hinweis, undefined)
|
||||
})
|
||||
|
||||
test('Stripe-Fehler: 502 mit festem Du-Text, nie der Stripe-Text; bei gescheiterter Kontoanlage bleibt kein Konto gespeichert', async () => {
|
||||
const feature = await aktiviere()
|
||||
const log = []
|
||||
const orig = console.error
|
||||
console.error = (...a) => log.push(a.join(' '))
|
||||
try {
|
||||
st.failNext({ method: 'POST', path: '/v1/accounts', status: 400, message: 'Geheimer Stripe-Text sk_live_123' })
|
||||
const a = await post('einrichten')
|
||||
assert.deepEqual([a.status, a.body.error], [502, GEDULD])
|
||||
assert.equal(JSON.stringify(a.body).includes('Geheim'), false)
|
||||
assert.equal(shopConfigDoc(feature).stripeAccountId || '', '')
|
||||
// Konto da, Link scheitert: Konto bleibt gespeichert, 502
|
||||
st.failNext({ method: 'POST', path: '/v1/account_links', status: 400, message: 'Geheimer Linktext' })
|
||||
const b = await post('einrichten')
|
||||
assert.deepEqual([b.status, b.body.error], [502, GEDULD])
|
||||
assert.match(shopConfigDoc(feature).stripeAccountId, /^acct_/)
|
||||
st.failNext({ method: 'POST', path: '/v1/account_links', status: 400, message: 'Geheimer Linktext' })
|
||||
assert.deepEqual([(await post('link')).status], [502])
|
||||
st.failNext({ method: 'GET', path: /^\/v1\/accounts\//, status: 400, message: 'Geheimer Lesetext' })
|
||||
const c = await post('aktualisieren')
|
||||
assert.deepEqual([c.status, c.body.error], [502, GEDULD])
|
||||
} finally {
|
||||
console.error = orig
|
||||
}
|
||||
assert.equal(log.some((l) => l.includes('Geheim') || l.includes('sk_live')), false)
|
||||
})
|
||||
|
||||
test('Kundenansicht: der Wächter sperrt die Routen (nicht auf der Erlaubnisliste), der Router selbst auch ohne Wächter (Kundenansicht und Ansehen als)', async () => {
|
||||
const feature = await aktiviere()
|
||||
for (const pfad of ['einrichten', 'link', 'aktualisieren']) {
|
||||
const res = await post(pfad, { projectId: P }, { cookie: kundenansichtCookie({ customerId: 'c1' }), headers: { 'X-Kundenansicht': 'a'.repeat(32) } })
|
||||
assert.deepEqual([res.status, res.body.code], [403, 'view_blocked'], pfad)
|
||||
}
|
||||
const express = (await import('express')).default
|
||||
const { sessionMiddleware } = await import('../server/middleware/session.js')
|
||||
const roh = express()
|
||||
roh.use(sessionMiddleware())
|
||||
roh.use(express.json())
|
||||
roh.use('/api/shop', shopRoutes)
|
||||
const server = await new Promise((resolve) => {
|
||||
const s = roh.listen(0, '127.0.0.1', () => resolve(s))
|
||||
})
|
||||
try {
|
||||
const url = `http://127.0.0.1:${server.address().port}`
|
||||
for (const pfad of ['einrichten', 'link', 'aktualisieren']) {
|
||||
for (const c of [kundenansichtCookie({ customerId: 'c1' }), viewAsCookie('c1')]) {
|
||||
const res = await fetch(`${url}/api/shop/stripe/${pfad}`, { method: 'POST', headers: { 'content-type': 'application/json', cookie: c }, body: JSON.stringify({ projectId: P }) })
|
||||
assert.equal(res.status, 403, pfad)
|
||||
assert.equal((await res.json()).error, 'In der Kundenansicht kann das Stripe-Konto nicht eingerichtet werden.')
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
await new Promise((resolve) => server.close(resolve))
|
||||
}
|
||||
assert.equal(st.calls.length, 0)
|
||||
assert.equal(shopConfigDoc(feature)?.stripeAccountId || '', '')
|
||||
})
|
||||
|
||||
test('Zugang und Zustand: fremdes Projekt 403, ohne Projekt 400, ohne Session 401, nicht eingerichtet/deaktiviert/Schalter aus 409', async () => {
|
||||
const feature = await aktiviere()
|
||||
assert.equal((await post('einrichten', { projectId: 'pt3' })).status, 403)
|
||||
assert.equal((await post('einrichten', {})).status, 400)
|
||||
assert.equal((await send('POST', '/api/shop/stripe/einrichten', { body: { projectId: P } })).status, 401)
|
||||
assert.equal((await post('einrichten', { projectId: 'pt2' })).status, 409)
|
||||
await updateDocument(config.collections.portalFeatures, feature.$id, sf.stateFields('disabled'))
|
||||
const aus = await post('einrichten')
|
||||
assert.deepEqual([aus.status, aus.body.code], [409, 'nicht_eingerichtet'])
|
||||
await updateDocument(config.collections.portalFeatures, feature.$id, sf.stateFields('off'))
|
||||
assert.equal((await post('aktualisieren')).status, 409)
|
||||
await updateDocument(config.collections.portalFeatures, feature.$id, sf.stateFields('active'))
|
||||
config.shop.mode = 'off'
|
||||
const schalter = await post('link')
|
||||
assert.deepEqual([schalter.status, schalter.body.code], [409, 'nicht_verfuegbar'])
|
||||
assert.equal(st.calls.length, 0)
|
||||
})
|
||||
|
||||
test('Rate-Limit: die 301. Schreibanfrage in der Stunde wird abgewiesen', async () => {
|
||||
await aktiviere()
|
||||
let letzter
|
||||
for (let i = 0; i < 301; i += 1) letzter = await post('link')
|
||||
assert.equal(letzter.status, 429)
|
||||
})
|
||||
Reference in new issue
Block a user