Files
Webklar-Kundenbereich/test/newsPosts.test.js
T

146 lines
8.8 KiB
JavaScript

import { test, after } from 'node:test'
import assert from 'node:assert/strict'
import os from 'node:os'
import path from 'node:path'
import { mkdtempSync } from 'node:fs'
import sharp from 'sharp'
import { startAppwriteMock } from './support/appwriteMock.js'
import { startFakeStripe } from './support/fakeStripe.js'
import { useTestEnv } from './support/testEnv.js'
import { schemaKeys } from './support/schema.js'
process.env.NEWS_UPLOAD_DIR = mkdtempSync(path.join(os.tmpdir(), 'news-posts-'))
const aw = await startAppwriteMock({ schema: { newsPosts: schemaKeys('newsPosts') } })
const st = await startFakeStripe()
after(() => Promise.all([aw.close(), st.close()]))
useTestEnv({ appwriteUrl: aw.url, stripeUrl: st.url })
const np = await import('../server/services/newsPosts.js')
const { storeNewsImage, newsImagePath } = await import('../server/services/newsImages.js')
const { existsSync } = await import('node:fs')
const { addMonths } = await import('../server/services/stripeBilling.js')
const NOW = new Date('2026-10-01T10:00:00.000Z')
const base = { type: 'note', title: 'Hallo', text: 'Wir haben offen', paper: 'sun', duration: '1m' }
const mk = (projectId, input = {}, now = NOW) => np.createPost({ customerId: 'c1', projectId, input: { ...base, ...input }, now })
const png = () => sharp({ create: { width: 40, height: 30, channels: 3, background: '#c33' } }).png().toBuffer()
test('ablauf_grenzen_berlin', async () => {
const until = np.visibleUntilFor('date', NOW, '2026-10-25')
assert.equal(until.toISOString(), '2026-10-25T22:59:59.999Z')
const post = { visibleFrom: NOW.toISOString(), visibleUntil: until.toISOString(), takenDownAt: null }
assert.equal(np.isHanging(post, new Date(until.getTime() - 1)), true)
assert.equal(np.isHanging(post, until), false)
const open = { visibleFrom: NOW.toISOString(), visibleUntil: null, takenDownAt: null }
assert.equal(np.isHanging(open, new Date('2030-01-01')), true)
assert.equal(np.isHanging({ ...open, takenDownAt: NOW.toISOString() }, new Date('2026-10-02')), false)
})
test('dauer_werte', () => {
assert.equal(np.visibleUntilFor('1w', NOW).getTime() - NOW.getTime(), 7 * 86400000)
assert.equal(np.visibleUntilFor('2w', NOW).getTime() - NOW.getTime(), 14 * 86400000)
const jan = new Date('2027-01-31T09:00:00.000Z')
assert.equal(np.visibleUntilFor('1m', jan).toISOString(), addMonths(jan, 1).toISOString())
assert.equal(np.visibleUntilFor('1m', jan).toISOString(), '2027-02-28T09:00:00.000Z')
assert.equal(np.visibleUntilFor('3m', NOW).toISOString(), addMonths(NOW, 3).toISOString())
assert.equal(np.visibleUntilFor('open', NOW), null)
})
test('datum_grenzen_400', () => {
assert.throws(() => np.visibleUntilFor('date', NOW, '2026-10-01'), { status: 400, field: 'date' })
assert.throws(() => np.visibleUntilFor('date', NOW, '2027-10-02'), { status: 400 })
assert.throws(() => np.visibleUntilFor('date', NOW, 'morgen'), { status: 400 })
assert.throws(() => np.visibleUntilFor('date', NOW, '2026-11-31'), { status: 400, field: 'date' })
assert.throws(() => np.visibleUntilFor('date', NOW, '2027-02-31'), { status: 400, field: 'date' })
assert.ok(np.visibleUntilFor('date', NOW, '2026-10-02'))
assert.ok(np.visibleUntilFor('date', NOW, '2027-10-01'))
})
test('validierung', () => {
assert.throws(() => np.validatePostInput({ ...base, title: 'x'.repeat(61) }), { status: 400, field: 'title' })
assert.throws(() => np.validatePostInput({ ...base, type: 'photo' }), { status: 400, field: 'imageId' })
assert.throws(() => np.validatePostInput({ ...base, paper: 'lila' }), { status: 400, field: 'paper' })
assert.throws(() => np.validatePostInput({ ...base, text: '' }), { status: 400, field: 'text' })
assert.equal(np.validatePostInput({ ...base, title: undefined }).title, '')
assert.equal(np.validatePostInput({ ...base, type: 'photo', title: '', text: '', imageId: 'i1' }).title, '')
assert.throws(() => np.validatePostInput({ ...base, text: 'x'.repeat(401) }), { status: 400, field: 'text' })
assert.throws(() => np.validatePostInput({ ...base, type: 'combo', imageId: 'i1', text: '' }), { status: 400, field: 'text' })
assert.equal(np.validatePostInput({ ...base, type: 'photo', text: '', imageId: 'i1' }).imageId, 'i1')
})
test('abnehmen_und_wieder_aufhaengen', async () => {
const post = await mk('pa')
assert.equal((await np.listPosts('pa', NOW)).hanging.length, 1)
const later = new Date('2026-10-05T08:00:00.000Z')
await np.updatePost({ customerId: 'c1', projectId: 'pa', id: post.$id, input: { action: 'takeDown' }, now: later })
const l1 = await np.listPosts('pa', later)
assert.equal(l1.hanging.length, 0)
assert.equal(l1.expired.length, 1)
const re = await np.updatePost({ customerId: 'c1', projectId: 'pa', id: post.$id, input: { action: 'rehang', duration: '2w' }, now: later })
assert.equal(re.visibleFrom, later.toISOString())
assert.equal(re.visibleUntil, new Date(later.getTime() + 14 * 86400000).toISOString())
assert.equal((await np.listPosts('pa', later)).hanging.length, 1)
})
test('oeffentlich_nur_haengende_ohne_interna', async () => {
for (let i = 0; i < 8; i++) await mk('pb', { title: `Beitrag ${i}` }, new Date(NOW.getTime() + i * 1000))
for (let i = 0; i < 2; i++) await mk('pb', { duration: '1w' }, new Date('2026-09-01T10:00:00.000Z'))
const { imageId } = await storeNewsImage({ customerId: 'c1', projectId: 'pb', buffer: await png() })
await mk('pb', { type: 'combo', imageId, title: 'Mit Foto' }, new Date(NOW.getTime() + 60000))
const after10 = new Date(NOW.getTime() + 120000)
const p1 = await np.publicPage('pb', 1, after10)
assert.equal(p1.posts.length, 6)
assert.equal(p1.hasMore, true)
assert.equal(p1.posts[0].title, 'Mit Foto')
assert.deepEqual({ ...p1.posts[0].image, v: undefined }, { w: 40, h: 30, v: undefined })
assert.ok(Number.isFinite(p1.posts[0].image.v), 'v = Cache-Buster fuer die Route')
assert.equal(p1.posts[0].date, '2026-10-01')
assert.equal(p1.posts[1].image, null)
for (const p of p1.posts) {
assert.deepEqual(Object.keys(p).sort(), ['date', 'id', 'image', 'paper', 'text', 'title', 'type'])
}
const p2 = await np.publicPage('pb', 2, after10)
assert.equal(p2.posts.length, 3)
assert.equal(p2.hasMore, false)
})
test('fremder_kunde_kann_nicht_aendern', async () => {
const post = await mk('pc')
await assert.rejects(np.updatePost({ customerId: 'c2', projectId: 'pc', id: post.$id, input: { action: 'takeDown' }, now: NOW }), { status: 403 })
await assert.rejects(np.updatePost({ customerId: 'c1', projectId: 'other', id: post.$id, input: { action: 'takeDown' }, now: NOW }), { status: 403 })
await assert.rejects(np.deletePost({ customerId: 'c2', projectId: 'pc', id: post.$id }), { status: 403 })
await assert.rejects(np.deletePost({ customerId: 'c1', projectId: 'pc', id: 'gibtsnicht' }), { status: 404 })
})
test('bild_gehoert_zum_projekt_und_wird_mitgeloescht', async () => {
const a = await storeNewsImage({ customerId: 'c1', projectId: 'pd', buffer: await png() })
await assert.rejects(mk('pe', { type: 'photo', imageId: a.imageId }), { status: 400, field: 'imageId' })
const post = await mk('pd', { type: 'photo', imageId: a.imageId })
const b = await storeNewsImage({ customerId: 'c1', projectId: 'pd', buffer: await png() })
await np.updatePost({ customerId: 'c1', projectId: 'pd', id: post.$id, input: { ...base, type: 'photo', imageId: b.imageId }, now: NOW })
assert.equal(existsSync(newsImagePath('pd', a.imageId, 'l')), false)
assert.equal(existsSync(newsImagePath('pd', b.imageId, 'l')), true)
await np.deletePost({ customerId: 'c1', projectId: 'pd', id: post.$id })
assert.equal(existsSync(newsImagePath('pd', b.imageId, 'l')), false)
assert.equal((await np.listPosts('pd', NOW)).hanging.length, 0)
})
test('bearbeiten_datum_bild_regeln', async () => {
const post = await mk('pf', { duration: 'date', date: '2026-10-10' })
const edit = (input, now) => np.updatePost({ customerId: 'c1', projectId: 'pf', id: post.$id, input: { ...base, duration: 'date', ...input }, now })
const late = new Date('2026-10-10T08:00:00.000Z')
// unveraendertes Datum (heute) bleibt speicherbar, geaendertes wird geprueft
const ok = await edit({ date: '2026-10-10', title: 'Neu' }, late)
assert.equal(ok.title, 'Neu')
assert.equal(ok.visibleUntil, post.visibleUntil)
await assert.rejects(edit({ date: '2026-10-11' }, new Date('2026-10-11T08:00:00.000Z')), { status: 400, field: 'date' })
// Bild eines anderen Beitrags
const img = await storeNewsImage({ customerId: 'c1', projectId: 'pf', buffer: await png() })
await mk('pf', { type: 'photo', imageId: img.imageId })
await assert.rejects(edit({ type: 'photo', imageId: img.imageId, date: '2026-10-10' }, late), { status: 400, field: 'imageId' })
})
test('create_ohne_bild_belegt_nichts', async () => {
await assert.rejects(mk('pg', { type: 'photo', imageId: 'nope' }), { status: 400, field: 'imageId' })
assert.equal((await np.listPosts('pg', NOW)).hanging.length, 0)
})