Live-Fehler: cs_live-Sitzungs-IDs (~66 Zeichen) passten nicht in das Feld portalFeatures.stripeSessionId (64), die Bestellung brach nach dem Anlegen der Stripe-Sitzung ab und der Kunde sah die Appwrite-Meldung. - Setup-Skript: Feld 128 Zeichen, bestehendes Feld wird per ensureStringSize vergroessert - Fehler von Appwrite oder unerwartete Fehler: Log + verstaendlicher Hinweis Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
314 lines
19 KiB
JavaScript
314 lines
19 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'
|
|
|
|
// Nachbauten starten und die Umgebung setzen, BEVOR config.js geladen wird
|
|
const aw = await startAppwriteMock({ schema: { subscriptions: ['customerId', 'status'] } })
|
|
const st = await startFakeStripe()
|
|
after(() => Promise.all([aw.close(), st.close()]))
|
|
useTestEnv({ appwriteUrl: aw.url, stripeUrl: st.url })
|
|
const { createDocument, getDocument, listDocuments, Query } = await import('../server/services/appwriteAdmin.js')
|
|
const { getStripe } = await import('../server/services/stripeBilling.js')
|
|
|
|
test('Appwrite-Nachbau: anlegen, lesen, filtern', async () => {
|
|
await createDocument('subscriptions', { customerId: 'c1', status: 'active' }, 'sub_a')
|
|
await createDocument('subscriptions', { customerId: 'c2', status: 'active' }, 'sub_c')
|
|
assert.equal((await getDocument('subscriptions', 'sub_a')).status, 'active')
|
|
const list = await listDocuments('subscriptions', [Query.equal('customerId', 'c1'), Query.limit(5)])
|
|
assert.deepEqual(list.map((d) => d.$id), ['sub_a'])
|
|
})
|
|
|
|
test('Appwrite-Nachbau: doppelte ID 409, fehlendes Dokument 404', async () => {
|
|
await assert.rejects(createDocument('subscriptions', { customerId: 'c1' }, 'sub_a'), (e) => e.status === 409)
|
|
await assert.rejects(getDocument('subscriptions', 'gibt_es_nicht'), (e) => e.status === 404)
|
|
})
|
|
|
|
test('Appwrite-Nachbau: Schema wie Appwrite', async () => {
|
|
await assert.rejects(createDocument('subscriptions', { unbekannt: 1 }, 'sub_b'), (e) => e.status === 400)
|
|
})
|
|
|
|
// Appwrite 1.8: Dokument-IDs hoechstens 36 Zeichen aus a-z, A-Z, 0-9, Punkt, Bindestrich, Unterstrich,
|
|
// nicht mit einem Sonderzeichen beginnend - sonst 400 (Task 7, Fix 1)
|
|
test('Appwrite-Nachbau: Dokument-ID nach Appwrite-Regeln', async () => {
|
|
const tooLong = 'abschluss-sub_1SD6vPRxAbCdEfGhIjKlMnOp' // 38 Zeichen
|
|
await assert.rejects(createDocument('subscriptions', { customerId: 'c1' }, tooLong), (e) => e.status === 400 && /documentId/.test(e.message))
|
|
await assert.rejects(createDocument('subscriptions', { customerId: 'c1' }, '-abc'), (e) => e.status === 400)
|
|
await assert.rejects(createDocument('subscriptions', { customerId: 'c1' }, 'abc/def'), (e) => e.status === 400)
|
|
const ok = await createDocument('subscriptions', { customerId: 'c1' }, `abschluss-${'f'.repeat(26)}`)
|
|
assert.equal(ok.$id.length, 36)
|
|
})
|
|
|
|
test('Appwrite-Nachbau: failNext liefert einmal den gewuenschten Fehler', async () => {
|
|
aw.failNext({ collection: 'subscriptions', method: 'POST', status: 500 })
|
|
await assert.rejects(createDocument('subscriptions', { customerId: 'c9' }, 'sub_fail'), (e) => e.status === 500)
|
|
assert.equal((await createDocument('subscriptions', { customerId: 'c9' }, 'sub_fail')).$id, 'sub_fail')
|
|
// Nur die passende Anfrage scheitert; andere Collections/Methoden laufen durch
|
|
aw.failNext({ collection: 'hourCredits', method: 'POST', status: 404 })
|
|
assert.equal((await getDocument('subscriptions', 'sub_fail')).$id, 'sub_fail')
|
|
await assert.rejects(createDocument('hourCredits', { customerId: 'c9' }, 'hc_1'), (e) => e.status === 404)
|
|
// after: so viele passende Anfragen gehen vorher noch durch (Task 8: erst die zweite PATCH scheitert).
|
|
// adminFetch bildet jeden 5xx-Status auf status 500 ab.
|
|
aw.failNext({ collection: 'subscriptions', method: 'GET', status: 503, after: 1 })
|
|
assert.equal((await getDocument('subscriptions', 'sub_fail')).$id, 'sub_fail')
|
|
await assert.rejects(getDocument('subscriptions', 'sub_fail'), (e) => e.status === 500 && /503/.test(e.message))
|
|
assert.equal((await getDocument('subscriptions', 'sub_fail')).$id, 'sub_fail')
|
|
})
|
|
|
|
test('Stripe-Nachbau: getStripe spricht mit 127.0.0.1', async () => {
|
|
st.subscriptions.set('sub_1', { id: 'sub_1', object: 'subscription', items: { object: 'list', data: [] } })
|
|
assert.equal((await getStripe().subscriptions.retrieve('sub_1')).id, 'sub_1')
|
|
assert.equal(st.calls.at(-1).path, '/v1/subscriptions/sub_1')
|
|
})
|
|
|
|
test('Stripe-Nachbau: unbekannte ID 404', async () => {
|
|
await assert.rejects(getStripe().prices.retrieve('price_weg'), (e) => e.statusCode === 404)
|
|
})
|
|
|
|
test('useTestEnv lehnt fremde Hosts ab', () => {
|
|
assert.throws(() => useTestEnv({ appwriteUrl: 'https://ticket.webklar.com/v1', stripeUrl: st.url }))
|
|
assert.equal(new URL(process.env.APPWRITE_ENDPOINT).hostname, '127.0.0.1')
|
|
})
|
|
|
|
// F17 (Task 8): POST /v1/subscriptions/:id mit Positionen (anlegen per Preis, aendern per ID), Metadaten
|
|
// (leerer Wert loescht wie bei Stripe) und einmaligem Zusatz nextSubscriptionUpdate fuer die Antwort
|
|
test('Stripe-Nachbau: Abo-Update mit Positionen, Metadaten, einmaliger Zusatz', async () => {
|
|
const stripe = getStripe()
|
|
st.prices.set('price_a', { id: 'price_a', object: 'price', active: true, lookup_key: 'lookup_a', unit_amount: 100 })
|
|
st.subscriptions.set('sub_u', {
|
|
id: 'sub_u',
|
|
object: 'subscription',
|
|
status: 'active',
|
|
metadata: { keep: '1', drop: 'x' },
|
|
items: { object: 'list', data: [{ id: 'si_1', object: 'subscription_item', quantity: 1, price: { id: 'price_0', object: 'price' } }] },
|
|
})
|
|
const added = await stripe.subscriptions.update('sub_u', {
|
|
items: [{ price: 'price_a', quantity: 2 }],
|
|
metadata: { drop: '', neu: 'y' },
|
|
proration_behavior: 'none',
|
|
})
|
|
assert.equal(added.items.data.length, 2)
|
|
const item = added.items.data[1]
|
|
assert.equal(item.quantity, 2)
|
|
assert.equal(item.price.id, 'price_a')
|
|
assert.equal(item.price.lookup_key, 'lookup_a')
|
|
assert.match(item.id, /^si_/)
|
|
assert.deepEqual(added.metadata, { keep: '1', neu: 'y' })
|
|
assert.equal(added.status, 'active')
|
|
assert.equal(st.calls.at(-1).body['items[0][price]'], 'price_a')
|
|
|
|
const changed = await stripe.subscriptions.update('sub_u', { items: [{ id: item.id, quantity: 0 }] })
|
|
assert.equal(changed.items.data[1].quantity, 0)
|
|
assert.equal((await stripe.subscriptions.retrieve('sub_u')).items.data[1].quantity, 0, 'gespeichert')
|
|
|
|
// Wie Stripe: derselbe Preis nicht zweimal, unbekannter Preis oder Posten 400/404, unbekanntes Abo 404
|
|
await assert.rejects(stripe.subscriptions.update('sub_u', { items: [{ price: 'price_a', quantity: 1 }] }), (e) => e.statusCode === 400)
|
|
await assert.rejects(stripe.subscriptions.update('sub_u', { items: [{ price: 'price_gibt_es_nicht', quantity: 1 }] }), (e) => e.statusCode === 400)
|
|
await assert.rejects(stripe.subscriptions.update('sub_u', { items: [{ id: 'si_gibt_es_nicht', quantity: 1 }] }), (e) => e.statusCode === 400)
|
|
await assert.rejects(stripe.subscriptions.update('sub_gibt_es_nicht', { metadata: { a: 'b' } }), (e) => e.statusCode === 404)
|
|
|
|
// Kuendigungsfelder wie cancelAtPeriodEnd/cancelSubscriptionAt sie setzen
|
|
const cancelled = await stripe.subscriptions.update('sub_u', { cancel_at_period_end: true, metadata: { cancelSource: 'test' } })
|
|
assert.equal(cancelled.cancel_at_period_end, true)
|
|
assert.equal(cancelled.metadata.cancelSource, 'test')
|
|
const dated = await stripe.subscriptions.update('sub_u', { cancel_at: 1800000000, proration_behavior: 'none' })
|
|
assert.equal(dated.cancel_at, 1800000000)
|
|
|
|
// nextSubscriptionUpdate: einmalig in die naechste Antwort gemischt, nicht gespeichert
|
|
st.nextSubscriptionUpdate = { latest_invoice: { status: 'paid', hosted_invoice_url: 'https://pay.example/inv' } }
|
|
const once = await stripe.subscriptions.update('sub_u', { metadata: { a: 'b' } })
|
|
assert.equal(once.latest_invoice.hosted_invoice_url, 'https://pay.example/inv')
|
|
assert.equal(once.metadata.a, 'b')
|
|
assert.equal((await stripe.subscriptions.retrieve('sub_u')).latest_invoice, undefined)
|
|
assert.equal((await stripe.subscriptions.update('sub_u', { metadata: { a: 'c' } })).latest_invoice, undefined)
|
|
assert.equal(st.nextSubscriptionUpdate, null)
|
|
})
|
|
|
|
// Task 9: mit pending_update im einmaligen Zusatz verhaelt sich der Nachbau wie Stripe bei einer ausstehenden
|
|
// Aktualisierung (Zahlung nicht erfolgt): das Abo bleibt unveraendert, die Aenderung wartet, bis ein Test sie
|
|
// mit applyPendingUpdate uebernimmt (= Rechnung bezahlt)
|
|
test('Stripe-Nachbau: ausstehende Aktualisierung bleibt zurückgehalten, bis sie übernommen wird', async () => {
|
|
const stripe = getStripe()
|
|
st.prices.set('price_b', { id: 'price_b', object: 'price', active: true, lookup_key: 'lookup_b', unit_amount: 500 })
|
|
st.subscriptions.set('sub_p', {
|
|
id: 'sub_p',
|
|
object: 'subscription',
|
|
status: 'active',
|
|
metadata: { productKey: 'hosting' },
|
|
items: { object: 'list', data: [{ id: 'si_p', object: 'subscription_item', quantity: 1, price: { id: 'price_0', object: 'price' } }] },
|
|
})
|
|
st.nextSubscriptionUpdate = { pending_update: { expires_at: 1 }, latest_invoice: { status: 'open', hosted_invoice_url: 'https://pay.example/inv' } }
|
|
const pending = await stripe.subscriptions.update('sub_p', {
|
|
items: [{ id: 'si_p', price: 'price_b' }],
|
|
metadata: { productKey: 'wartung' },
|
|
payment_behavior: 'pending_if_incomplete',
|
|
})
|
|
assert.deepEqual(pending.pending_update, { expires_at: 1 })
|
|
assert.equal(pending.latest_invoice.hosted_invoice_url, 'https://pay.example/inv')
|
|
// Wie Stripe: Antwort und gespeichertes Abo zeigen den alten Stand
|
|
assert.equal(pending.metadata.productKey, 'hosting')
|
|
assert.equal(pending.items.data[0].price.id, 'price_0')
|
|
// Wie Stripe (Fix 1): das Abo traegt pending_update, bis die Aenderung uebernommen oder verworfen wird
|
|
const stored = await stripe.subscriptions.retrieve('sub_p')
|
|
assert.equal(stored.metadata.productKey, 'hosting')
|
|
assert.deepEqual(stored.pending_update, { expires_at: 1 })
|
|
// Bezahlt: die zurueckgehaltene Aenderung wird uebernommen
|
|
const applied = st.applyPendingUpdate('sub_p')
|
|
assert.equal(applied.metadata.productKey, 'wartung')
|
|
const after = await stripe.subscriptions.retrieve('sub_p')
|
|
assert.equal(after.items.data[0].price.id, 'price_b')
|
|
assert.equal(after.pending_update, null)
|
|
assert.throws(() => st.applyPendingUpdate('sub_p'), /keine ausstehende/)
|
|
// Verfallen: Stripe verwirft die Aenderung, das Abo bleibt beim alten Stand
|
|
st.nextSubscriptionUpdate = { pending_update: { expires_at: 2 } }
|
|
await stripe.subscriptions.update('sub_p', { metadata: { productKey: 'neu' }, payment_behavior: 'pending_if_incomplete' })
|
|
st.expirePendingUpdate('sub_p')
|
|
const expired = await stripe.subscriptions.retrieve('sub_p')
|
|
assert.equal(expired.metadata.productKey, 'wartung')
|
|
assert.equal(expired.pending_update, null)
|
|
assert.throws(() => st.applyPendingUpdate('sub_p'), /keine ausstehende/)
|
|
})
|
|
|
|
// Wie Stripe (docs.stripe.com/billing/subscriptions/pending-updates, Stand 28.09.2026): mit
|
|
// pending_if_incomplete nur die unterstuetzten Parameter; Metadaten hoechstens 40 Zeichen je Schluessel und
|
|
// 500 je Wert
|
|
test('Stripe-Nachbau: pending_if_incomplete nur mit unterstützten Parametern, Metadaten mit Stripe-Grenzen', async () => {
|
|
const stripe = getStripe()
|
|
st.subscriptions.set('sub_q', {
|
|
id: 'sub_q',
|
|
object: 'subscription',
|
|
status: 'active',
|
|
metadata: {},
|
|
items: { object: 'list', data: [{ id: 'si_q', object: 'subscription_item', quantity: 1, price: { id: 'price_0', object: 'price' } }] },
|
|
})
|
|
await assert.rejects(
|
|
stripe.subscriptions.update('sub_q', { payment_behavior: 'pending_if_incomplete', description: 'nicht erlaubt' }),
|
|
(e) => e.statusCode === 400 && /description/.test(e.message)
|
|
)
|
|
await assert.rejects(
|
|
stripe.subscriptions.update('sub_q', { payment_behavior: 'pending_if_incomplete', items: [{ id: 'si_q', metadata: { x: '1' } }] }),
|
|
(e) => e.statusCode === 400 && /metadata/.test(e.message)
|
|
)
|
|
const ok = await stripe.subscriptions.update('sub_q', {
|
|
payment_behavior: 'pending_if_incomplete',
|
|
items: [{ id: 'si_q', quantity: 2 }],
|
|
billing_cycle_anchor: 'now',
|
|
proration_behavior: 'create_prorations',
|
|
proration_date: 1790000000,
|
|
metadata: { a: 'b' },
|
|
discounts: [{ coupon: 'X' }],
|
|
expand: ['latest_invoice'],
|
|
})
|
|
assert.equal(ok.items.data[0].quantity, 2)
|
|
await assert.rejects(stripe.subscriptions.update('sub_q', { metadata: { a: 'x'.repeat(501) } }), (e) => e.statusCode === 400)
|
|
await assert.rejects(stripe.subscriptions.update('sub_q', { metadata: { ['k'.repeat(41)]: '1' } }), (e) => e.statusCode === 400)
|
|
assert.equal((await stripe.subscriptions.update('sub_q', { metadata: { a: 'x'.repeat(500) } })).metadata.a.length, 500)
|
|
})
|
|
|
|
// F25: Mails lassen sich in Tests pruefen (nodemailer jsonTransport statt SMTP); null schaltet zurueck
|
|
test('Mail-Senke: Kunden- und Team-Mails landen in der Senke, null schaltet zurück', async () => {
|
|
const { mailSink } = await import('./support/mailSink.js')
|
|
const { sendAdminNotice, sendCustomerMail, setMailTransportForTests } = await import('../server/services/mailer.js')
|
|
const sink = mailSink()
|
|
setMailTransportForTests(sink)
|
|
try {
|
|
await sendCustomerMail('k@example.invalid', {
|
|
template: 'K10',
|
|
values: { name: 'K', antrag_datum: '01.10.2026', antrag_nr: 'A1', begruendung: 'Nachweis fehlt' },
|
|
attachments: [{ filename: 'a.pdf', content: Buffer.from('PDF'), contentType: 'application/pdf' }],
|
|
})
|
|
assert.deepEqual(await sendAdminNotice({ template: 'T06', values: { email: 'k@example.invalid', produkt: 'Hosting-Abo', stripe_abo_id: 'sub_1' } }), { sent: true })
|
|
assert.equal(sink.sent.length, 2)
|
|
const [customerMail, notice] = sink.sent
|
|
assert.deepEqual(customerMail.to.map((t) => t.address), ['k@example.invalid'])
|
|
assert.equal(customerMail.from.name, 'WEBklar')
|
|
assert.match(customerMail.text, /Nachweis fehlt/)
|
|
assert.deepEqual(customerMail.attachments.map((a) => a.filename), ['a.pdf'])
|
|
assert.equal(notice.from.name, 'WEBklar Kundenportal')
|
|
assert.match(notice.subject, /^\[Abo\]/)
|
|
} finally {
|
|
setMailTransportForTests(null)
|
|
}
|
|
await assert.rejects(sendCustomerMail('k@example.invalid', { template: 'K10', values: {} }), /SMTP nicht konfiguriert/)
|
|
})
|
|
|
|
// F17: GET /v1/invoices/:id bettet wie Stripe hoechstens 10 Zeilen ein, GET /v1/invoices/:id/lines blaettert
|
|
test('Stripe-Nachbau: Rechnung bettet höchstens 10 Zeilen ein, /lines blättert', async () => {
|
|
const stripe = getStripe()
|
|
st.invoices.set('in_p', { id: 'in_p', object: 'invoice', lines: { object: 'list', data: Array.from({ length: 12 }, (_, i) => ({ amount: i })) } })
|
|
const invoice = await stripe.invoices.retrieve('in_p')
|
|
assert.equal(invoice.lines.data.length, 10)
|
|
assert.equal(invoice.lines.has_more, true)
|
|
assert.ok(invoice.lines.data.every((l) => l.id && l.object === 'line_item'))
|
|
const page = await stripe.invoices.listLineItems('in_p', { limit: 5 })
|
|
assert.equal(page.data.length, 5)
|
|
assert.equal(page.has_more, true)
|
|
const all = await stripe.invoices.listLineItems('in_p', { limit: 5 }).autoPagingToArray({ limit: 100 })
|
|
assert.deepEqual(all.map((l) => l.amount), [...Array(12).keys()])
|
|
const small = await stripe.invoices.retrieve('in_p') // unveraendert seedbar: die Quelle behaelt alle Zeilen
|
|
assert.equal(small.lines.data.length, 10)
|
|
st.invoices.set('in_q', { id: 'in_q', object: 'invoice', lines: { object: 'list', data: [{ amount: 1 }] } })
|
|
assert.equal((await stripe.invoices.retrieve('in_q')).lines.has_more, false)
|
|
await assert.rejects(stripe.invoices.listLineItems('in_nix'), (e) => e.statusCode === 404)
|
|
})
|
|
|
|
// F17: POST /v1/invoices/create_preview liefert die vorab hinterlegte Vorschau (Objekt oder Funktion des Aufrufs)
|
|
test('Stripe-Nachbau: Rechnungsvorschau aus dem Seed', async () => {
|
|
const stripe = getStripe()
|
|
const params = { subscription: 'sub_u', subscription_details: { items: [{ id: 'si_1', quantity: 2 }], proration_behavior: 'create_prorations', proration_date: 1 } }
|
|
const empty = await stripe.invoices.createPreview(params)
|
|
assert.deepEqual(empty.lines.data, [])
|
|
st.invoicePreview = { object: 'invoice', lines: { object: 'list', data: [{ amount: 42 }], has_more: false } }
|
|
assert.equal((await stripe.invoices.createPreview(params)).lines.data[0].amount, 42)
|
|
assert.equal(st.calls.at(-1).body['subscription_details[items][0][quantity]'], '2')
|
|
assert.equal(st.calls.at(-1).body['subscription_details[proration_behavior]'], 'create_prorations')
|
|
st.invoicePreview = (body) => ({ object: 'invoice', lines: { data: [{ amount: Number(body.subscription_details.items[0].quantity) * 100 }] } })
|
|
assert.equal((await stripe.invoices.createPreview(params)).lines.data[0].amount, 200)
|
|
await assert.rejects(stripe.invoices.createPreview({ subscription: 'sub_nix' }), (e) => e.statusCode === 404)
|
|
st.invoicePreview = null
|
|
})
|
|
|
|
// F41d / Task 8 (Fix 1): Fehler im Stripe-Nachbau einplanen - einmalig, nur fuer den passenden Aufruf
|
|
// (method, path als String oder RegExp); after passende Aufrufe gehen vorher durch. Der gescheiterte Aufruf
|
|
// steht trotzdem in calls; 4xx wiederholt stripe-node nicht (5xx schon, maxNetworkRetries).
|
|
test('Stripe-Nachbau: failNext lässt den nächsten passenden Aufruf scheitern', async () => {
|
|
const stripe = getStripe()
|
|
st.subscriptions.set('sub_f', { id: 'sub_f', object: 'subscription', status: 'active', metadata: {}, items: { object: 'list', data: [] } })
|
|
st.failNext({ method: 'POST', path: '/v1/subscriptions/sub_f', status: 400, message: 'Nachbau: abgelehnt' })
|
|
await assert.rejects(stripe.subscriptions.update('sub_f', { metadata: { a: 'b' } }), (e) => e.statusCode === 400 && /abgelehnt/.test(e.message))
|
|
assert.equal(st.calls.at(-1).body['metadata[a]'], 'b', 'gescheiterter Aufruf steht in calls')
|
|
assert.equal((await stripe.subscriptions.retrieve('sub_f')).metadata.a, undefined, 'gescheiterter Aufruf aendert nichts')
|
|
assert.equal((await stripe.subscriptions.update('sub_f', { metadata: { a: 'b' } })).metadata.a, 'b', 'einmalig')
|
|
// Nur der passende Pfad (RegExp) und erst nach `after` passenden Aufrufen
|
|
st.failNext({ method: 'POST', path: /\/v1\/subscriptions\/sub_f$/, status: 402, after: 1 })
|
|
assert.equal((await stripe.subscriptions.retrieve('sub_f')).id, 'sub_f')
|
|
assert.equal((await stripe.subscriptions.update('sub_f', { metadata: { a: 'c' } })).metadata.a, 'c')
|
|
await assert.rejects(stripe.subscriptions.update('sub_f', { metadata: { a: 'd' } }), (e) => e.statusCode === 402)
|
|
assert.equal((await stripe.subscriptions.retrieve('sub_f')).metadata.a, 'c')
|
|
})
|
|
|
|
test('schema_funktionen_news', async () => {
|
|
const { schemaKeys } = await import('./support/schema.js')
|
|
const features = schemaKeys('portalFeatures')
|
|
for (const k of ['customerId', 'projectId', 'featureKey', 'enabled', 'unlockedByPurchase', 'purchaseStatus', 'state', 'siteKey', 'ticketId', 'stripeSessionId', 'stripeInvoiceId', 'invoiceUrl', 'orderedAt', 'activatedAt', 'disabledAt', 'consentText', 'consentAt', 'consentIp', 'consentUserAgent', 'adminNote']) {
|
|
assert.ok(features.includes(k), `portalFeatures.${k}`)
|
|
}
|
|
const news = schemaKeys('newsPosts')
|
|
for (const k of ['customerId', 'projectId', 'type', 'title', 'text', 'imageId', 'paper', 'duration', 'visibleFrom', 'visibleUntil', 'takenDownAt', 'createdAt', 'updatedAt']) {
|
|
assert.ok(news.includes(k), `newsPosts.${k}`)
|
|
}
|
|
})
|
|
|
|
// Stripe-Checkout-IDs im Live-Modus sind ~66 Zeichen lang (cs_live_ + 58); 64 war zu knapp
|
|
test('schema_stripe_session_id_lang_genug', async () => {
|
|
const { readFileSync } = await import('node:fs')
|
|
const src = readFileSync(new URL('../scripts/setup-abo-collections.mjs', import.meta.url), 'utf8')
|
|
const block = src.slice(src.indexOf("'portalFeatures',"))
|
|
const m = block.match(/key: 'stripeSessionId', size: (\d+)/)
|
|
assert.ok(m, 'stripeSessionId im portalFeatures-Block')
|
|
assert.ok(Number(m[1]) >= 128, `stripeSessionId size ${m[1]} < 128`)
|
|
// bestehendes Feld in Produktion wird per Groessen-Update angehoben
|
|
assert.match(src, /ensureStringSize\('portalFeatures', 'stripeSessionId', 128\)/)
|
|
})
|