99 lines
5.1 KiB
JavaScript
99 lines
5.1 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 { startAppwriteMock } from './support/appwriteMock.js'
|
|
import { startFakeStripe } from './support/fakeStripe.js'
|
|
import { useTestEnv } from './support/testEnv.js'
|
|
import { mailSink } from './support/mailSink.js'
|
|
|
|
// Chat ueber den Team-Eingang (/api/team, JWT) mit signierten Anhang-Links
|
|
process.env.CHAT_UPLOAD_DIR = mkdtempSync(path.join(os.tmpdir(), 'team-chat-'))
|
|
const aw = await startAppwriteMock()
|
|
const st = await startFakeStripe()
|
|
useTestEnv({ appwriteUrl: aw.url, stripeUrl: st.url })
|
|
const { startTestApp, rawGet, post } = await import('./support/testApp.js')
|
|
const { seedFixtures } = await import('./support/fixtures.js')
|
|
const { setMailTransportForTests } = await import('../server/services/mailer.js')
|
|
const { default: teamRoutes } = await import('../server/routes/team/index.js')
|
|
const app = await startTestApp({ '/api/team': teamRoutes })
|
|
const sink = mailSink()
|
|
setMailTransportForTests(sink)
|
|
after(() => Promise.all([app.close(), aw.close(), st.close()]))
|
|
|
|
seedFixtures(aw)
|
|
aw.seed('employees', { $id: 'e_staff2', userId: 'u_staff2', displayName: 'Andrej', email: 'andrej@webklar.com' })
|
|
aw.seed('portalMessages', { $id: 'm1', customerId: 'c1', sender: 'customer', senderName: 'Kunde Eins', text: 'Frage 1', readByAdmin: false, readByCustomer: true, createdAt: '2026-10-01T08:00:00.000Z' })
|
|
aw.seed('portalMessages', { $id: 'm2', customerId: 'c1', sender: 'customer', senderName: 'Kunde Eins', text: 'Frage 2', readByAdmin: false, readByCustomer: true, createdAt: '2026-10-01T08:01:00.000Z' })
|
|
aw.seed('portalMessages', { $id: 'm3', customerId: 'c2', sender: 'admin', senderName: 'Kenso', text: 'Hallo c2', readByAdmin: true, readByCustomer: false, createdAt: '2026-10-01T09:00:00.000Z' })
|
|
|
|
const staff = { headers: { authorization: 'Bearer jwt-staff' } }
|
|
const upload = (customerId, body, headers = {}) =>
|
|
fetch(`${app.url}/api/team/chats/${customerId}/attachments`, {
|
|
method: 'POST',
|
|
headers: { authorization: 'Bearer jwt-staff', 'content-type': 'application/octet-stream', 'x-file-name': 'notiz.txt', ...headers },
|
|
body,
|
|
}).then(async (res) => ({ status: res.status, body: await res.json() }))
|
|
const URL_RE = /^https:\/\/project\.webklar\.com\/api\/team\/chats\/c1\/attachments\/([^?]+)\?exp=(\d+)&sig=([0-9a-f]{64})$/
|
|
|
|
test('Liste und ungelesene Zahl ueber den Team-Eingang', async () => {
|
|
const list = await rawGet('/api/team/chats', staff)
|
|
assert.equal(list.status, 200)
|
|
const c1 = list.body.chats.find((c) => c.customerId === 'c1')
|
|
assert.equal(c1.unreadFromCustomer, 2)
|
|
assert.equal(c1.customerName, 'Kunde Eins')
|
|
assert.equal((await rawGet('/api/team/chats/unread-count', staff)).body.unread, 2)
|
|
})
|
|
|
|
test('Senden als Mitarbeiter: Absendername aus dem JWT, K17-Mail an den Kunden', async () => {
|
|
const res = await post('/api/team/chats/c1/messages', { text: 'Hallo' }, staff)
|
|
assert.equal(res.status, 201)
|
|
assert.equal(res.body.message.senderName, 'Andrej')
|
|
assert.equal(res.body.message.sender, 'admin')
|
|
assert.equal(res.body.notice.sent, true)
|
|
assert.equal(sink.sent.at(-1).to[0].address, 'k@example.invalid')
|
|
})
|
|
|
|
let fileUrl = null
|
|
test('Anhang hochladen, mit Nachricht senden -> signierte url', async () => {
|
|
const up = await upload('c1', Buffer.from('hallo welt'))
|
|
assert.equal(up.status, 201)
|
|
const sent = await post('/api/team/chats/c1/messages', { text: '', attachmentIds: [up.body.attachment.id] }, staff)
|
|
assert.equal(sent.status, 201)
|
|
fileUrl = sent.body.message.attachments[0].url
|
|
assert.match(fileUrl, URL_RE)
|
|
assert.equal(URL_RE.exec(fileUrl)[1], up.body.attachment.id)
|
|
const listed = await rawGet('/api/team/chats/c1/messages', staff)
|
|
assert.match(listed.body.messages.at(-1).attachments[0].url, URL_RE)
|
|
})
|
|
|
|
test('signierter Link: ohne JWT 200, manipuliert/fremder Kunde/abgelaufen 403, ohne sig 401, JWT ohne sig 200', async (t) => {
|
|
const u = new URL(fileUrl)
|
|
const rel = u.pathname + u.search
|
|
const ok = await fetch(app.url + rel)
|
|
assert.equal(ok.status, 200)
|
|
assert.equal(await ok.text(), 'hallo welt')
|
|
const tampered = rel.replace(/sig=([0-9a-f])/, (m, c) => `sig=${c === 'a' ? 'b' : 'a'}`)
|
|
assert.equal((await rawGet(tampered)).status, 403)
|
|
assert.equal((await rawGet(rel.replace('/chats/c1/', '/chats/c2/'))).status, 403)
|
|
assert.equal((await rawGet(u.pathname)).status, 401)
|
|
assert.equal((await rawGet(u.pathname, staff)).status, 200)
|
|
const now = Date.now()
|
|
t.mock.method(Date, 'now', () => now + 16 * 60 * 1000)
|
|
const expired = await rawGet(rel)
|
|
assert.equal(expired.status, 403)
|
|
assert.equal(expired.body.error, 'Link abgelaufen. Bitte die Unterhaltung neu laden.')
|
|
})
|
|
|
|
test('Lesen markiert Kundennachrichten', async () => {
|
|
const res = await post('/api/team/chats/c1/read', {}, staff)
|
|
assert.equal(res.body.updated, 2)
|
|
assert.equal((await rawGet('/api/team/chats/unread-count', staff)).body.unread, 0)
|
|
})
|
|
|
|
test('Team-Chat ohne JWT -> 401', async () => {
|
|
assert.equal((await rawGet('/api/team/chats')).status, 401)
|
|
assert.equal((await post('/api/team/chats/c1/messages', { text: 'x' })).status, 401)
|
|
})
|