134 lines
6.1 KiB
JavaScript
134 lines
6.1 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'
|
||
|
||
// websiteProjects, workorders und customers legt das Setup-Skript nicht an: ohne Schema im Nachbau
|
||
const aw = await startAppwriteMock({ schema: { portalFeatures: schemaKeys('portalFeatures') } })
|
||
const st = await startFakeStripe()
|
||
after(() => Promise.all([aw.close(), st.close()]))
|
||
useTestEnv({ appwriteUrl: aw.url, stripeUrl: st.url })
|
||
const sf = await import('../server/services/siteFeatures.js')
|
||
|
||
aw.seed('customers', { $id: 'c1', name: 'Kunde Eins', location: 'Berlin' })
|
||
aw.seed('customers', { $id: 'c2', name: 'Kunde Zwei' })
|
||
aw.seed('websiteProjects', { $id: 'p1', customerId: 'c1', projectName: 'Bäckerei', liveDomain: 'baeckerei.de' })
|
||
aw.seed('websiteProjects', { $id: 'p2', customerId: 'c1', projectName: 'Café', subdomain: 'cafe' })
|
||
aw.seed('websiteProjects', { $id: 'p3', customerId: 'c2', projectName: 'Fremd' })
|
||
aw.seed('websiteProjects', { $id: 'p_' + 'x'.repeat(40), customerId: 'c1', projectName: 'Lang' })
|
||
|
||
const consent = { text: 'Ich stimme zu', at: '2026-09-30T10:00:00.000Z', ip: '1.2.3.4', userAgent: 'UA' }
|
||
const tickets = () => [...aw.docs('workorders').values()].filter((t) => String(t.topic).startsWith('News-Pinnwand einbauen'))
|
||
|
||
test('ohne_dokument_ist_off', async () => {
|
||
const list = await sf.listCustomerFeatures('c1')
|
||
assert.equal(list.length, 3)
|
||
for (const p of list) assert.equal(p.features.news.state, 'off')
|
||
assert.equal(list.find((p) => p.projectId === 'p1').liveUrl, 'https://baeckerei.de')
|
||
})
|
||
|
||
test('bezahlt_legt_ticket_an', async () => {
|
||
const ordered = await sf.markOrdered({ customerId: 'c1', projectId: 'p1', key: 'news', sessionId: 'cs_1', consent })
|
||
assert.equal(ordered.state, 'ordered')
|
||
assert.equal(ordered.enabled, false)
|
||
assert.equal(ordered.consentIp, '1.2.3.4')
|
||
assert.match(ordered.siteKey, /^[a-z2-7]{16}$/)
|
||
const { doc, created } = await sf.markPaid({ sessionId: 'cs_1', invoiceId: 'in_1', invoiceUrl: 'https://x/inv' })
|
||
assert.equal(created, true)
|
||
assert.equal(doc.state, 'setup')
|
||
assert.equal(doc.enabled, false)
|
||
assert.equal(doc.unlockedByPurchase, true)
|
||
assert.ok(doc.ticketId)
|
||
assert.equal(tickets().length, 1)
|
||
assert.match(tickets()[0].topic, /^News-Pinnwand einbauen – Bäckerei/)
|
||
assert.ok(tickets()[0].details.includes(`data-webklar-news="${doc.siteKey}"`))
|
||
const view = (await sf.listCustomerFeatures('c1')).find((p) => p.projectId === 'p1').features.news
|
||
assert.equal(view.state, 'setup')
|
||
assert.ok(view.ticketWoid)
|
||
})
|
||
|
||
test('webhook_idempotent', async () => {
|
||
const again = await sf.markPaid({ sessionId: 'cs_1', invoiceId: 'in_1', invoiceUrl: 'https://x/inv' })
|
||
assert.equal(again.created, false)
|
||
assert.equal(tickets().length, 1)
|
||
})
|
||
|
||
test('ticket_geschlossen_aktiviert', async () => {
|
||
const doc = await sf.getFeatureDoc('c1', 'p1', 'news')
|
||
assert.deepEqual(await sf.activateClosedTickets({ customerId: 'c1' }), [])
|
||
assert.equal((await sf.getFeatureDoc('c1', 'p1', 'news')).state, 'setup')
|
||
aw.seed('workorders', { ...aw.docs('workorders').get(doc.ticketId), status: 'Closed' })
|
||
assert.deepEqual(await sf.activateClosedTickets({ customerId: 'c2' }), [])
|
||
const now = new Date('2026-10-01T08:00:00Z')
|
||
const done = await sf.activateClosedTickets({ now })
|
||
assert.equal(done.length, 1)
|
||
assert.equal(done[0].state, 'active')
|
||
assert.equal(done[0].enabled, true)
|
||
assert.equal(done[0].activatedAt, now.toISOString())
|
||
assert.equal((await sf.findActiveBySiteKey(doc.siteKey)).$id, doc.$id)
|
||
})
|
||
|
||
test('fehlgeschlagene_zahlung_setzt_off', async () => {
|
||
await sf.markOrdered({ customerId: 'c1', projectId: 'p2', key: 'news', sessionId: 'cs_2', consent })
|
||
const doc = await sf.markCheckoutFailed('cs_2')
|
||
assert.equal(doc.state, 'off')
|
||
assert.equal(doc.enabled, false)
|
||
assert.equal(await sf.markCheckoutFailed('cs_unbekannt'), null)
|
||
})
|
||
|
||
test('fremdes_projekt_403', async () => {
|
||
await assert.rejects(() => sf.assertOwnProject('c1', 'p3'), { status: 403 })
|
||
await assert.rejects(() => sf.assertOwnProject('c1', 'gibts_nicht'), { status: 403 })
|
||
assert.equal((await sf.assertOwnProject('c1', 'p1')).$id, 'p1')
|
||
})
|
||
|
||
test('sitekey_format', () => {
|
||
const keys = new Set()
|
||
for (let i = 0; i < 100; i += 1) {
|
||
const k = sf.newSiteKey()
|
||
assert.match(k, /^[a-z2-7]{16}$/)
|
||
keys.add(k)
|
||
}
|
||
assert.equal(keys.size, 100)
|
||
})
|
||
|
||
test('admin_aktiviert_ohne_zahlung', async () => {
|
||
const before = tickets().length
|
||
const doc = await sf.adminActivate('c1', 'p2', 'news', { by: 'kenso' })
|
||
assert.equal(doc.state, 'active')
|
||
assert.equal(doc.enabled, true)
|
||
assert.equal(doc.unlockedByPurchase, false)
|
||
assert.equal(tickets().length, before)
|
||
const off = await sf.disable(doc.$id, { by: 'kenso' })
|
||
assert.equal(off.state, 'disabled')
|
||
assert.equal(off.enabled, false)
|
||
})
|
||
|
||
test('lange_projekt_id_bekommt_gueltige_dokument_id', async () => {
|
||
const doc = await sf.adminActivate('c1', 'p_' + 'x'.repeat(40), 'news', {})
|
||
assert.ok(doc.$id.length <= 36)
|
||
})
|
||
|
||
test('bestellen_bei_aktiver_funktion_409', async () => {
|
||
const doc = await sf.adminActivate('c1', 'p1', 'news', {}).catch(() => null)
|
||
const active = doc || (await sf.getFeatureDoc('c1', 'p1', 'news'))
|
||
assert.equal(active.state, 'active')
|
||
await assert.rejects(
|
||
() => sf.markOrdered({ customerId: 'c1', projectId: 'p1', key: 'news', sessionId: 'cs_x', consent }),
|
||
{ status: 409 }
|
||
)
|
||
assert.equal((await sf.getFeatureDoc('c1', 'p1', 'news')).state, 'active')
|
||
})
|
||
|
||
test('parallele_webhooks_ein_ticket', async () => {
|
||
aw.seed('websiteProjects', { $id: 'p4', customerId: 'c1', projectName: 'Parallel' })
|
||
await sf.markOrdered({ customerId: 'c1', projectId: 'p4', key: 'news', sessionId: 'cs_par', consent })
|
||
const before = tickets().length
|
||
const res = await Promise.all([1, 2, 3].map(() => sf.markPaid({ sessionId: 'cs_par', invoiceId: 'in_p', invoiceUrl: 'u' })))
|
||
assert.equal(res.filter((r) => r.created).length, 1)
|
||
assert.equal(tickets().length, before + 1)
|
||
assert.ok((await sf.getFeatureDoc('c1', 'p4', 'news')).ticketId)
|
||
})
|