import assert from "node:assert/strict"
import test from "node:test"
import { randomUUID } from "node:crypto"
import sharp from "sharp"
import Fastify from "fastify"
import { chatContent } from "../src/modules/chat-content"
import { CollaborationService } from "../src/modules/collaboration/service"
import communicationRoutes from "../src/routes/communication"
import { collaborationDatabase, testDatabaseUrl } from "./helpers/collaboration-db"
const picture = async () => ({ name: "Baustelle.png", dataUrl: `data:image/png;base64,${(await sharp({ create: { width: 80, height: 40, channels: 3, background: '#206050' } }).png().toBuffer()).toString('base64')}` })
test("chat content accepts emoji and image-only messages, strips metadata and rejects unsafe input", async () => {
assert.equal((await chatContent({ text: "Hallo 👋 ❤️" })).body, "Hallo 👋 ❤️")
const normalized = await chatContent({ image: await picture() })
assert.equal(normalized.body, "")
assert.equal(normalized.image?.width, 80)
const metadata = await sharp(Buffer.from(normalized.image!.dataUrl.split(',')[1], 'base64')).metadata()
assert.equal(metadata.format, 'webp')
assert.equal(metadata.exif, undefined)
for (const input of [{}, { text: ' '.repeat(5) }, { text: 'a'.repeat(10001) },
{ image: { dataUrl: 'https://example.test/tracker.png' } },
{ image: { dataUrl: `data:image/png;base64,${Buffer.from('').toString('base64')}` } },
{ image: { dataUrl: 'data:image/png;base64,broken' } },
{ image: { dataUrl: 'a'.repeat(700001) } }]) await assert.rejects(chatContent(input))
})
test("native chat images and reactions follow tenant, membership, project and message boundaries", { skip: !testDatabaseUrl }, async () => {
const fixture = await collaborationDatabase(), app = Fastify()
const owner = randomUUID(), colleague = randomUUID(), outsider = randomUUID(), room = randomUUID(), otherRoom = randomUUID()
try {
await fixture.pool.query("INSERT INTO tenants VALUES (1,'A',null),(2,'B',null)")
for (const [id, tenant] of [[owner, 1], [colleague, 1], [outsider, 2]]) {
await fixture.pool.query("INSERT INTO auth_users(id,email) VALUES ($1,$2)", [id, `${id}@test.example`])
await fixture.pool.query("INSERT INTO auth_tenant_users(tenant_id,user_id) VALUES ($1,$2)", [tenant, id])
}
await fixture.pool.query("INSERT INTO projects(id,tenant,name,profiles,archived,chat_enabled) VALUES (1,1,'Project','{}',false,true)")
await fixture.pool.query(`INSERT INTO communication_rooms(id,tenant_id,key,name,type,archived,entity_id)
VALUES ($1,1,'project_1','Project','project',false,1),($2,1,'other','Other','room',false,null)`, [room, otherRoom])
await fixture.pool.query("INSERT INTO communication_room_members(room_id,user_id) VALUES ($1,$2),($1,$3),($4,$2)", [room, owner, colleague, otherRoom])
app.decorate('db', fixture.db)
app.addHook('preHandler', async (req: any) => {
req.user = { user_id: req.headers['x-user'] || owner, tenant_id: Number(req.headers['x-tenant'] || 1), is_admin: true }
req.hasPermission = () => true
})
await app.register(communicationRoutes)
const base = '/communication/chat/rooms/project_1'
const sent = await app.inject({ method: 'POST', url: `${base}/messages`, payload: { image: await picture() } })
assert.equal(sent.statusCode, 200, sent.body)
const message = sent.json()
assert.equal(message.body, '')
assert.equal(message.image.width, 80)
assert.equal(message.image.dataUrl, undefined)
assert.equal((await app.inject(`${base}/messages`)).json().messages[0].image.dataUrl, undefined)
const image = await app.inject(`${base}/image?messageId=${message.id}`)
assert.equal(image.statusCode, 200, image.body)
assert.equal(image.headers['cache-control'], 'no-store')
assert.match(image.json().dataUrl, /^data:image\/webp;base64,/)
const react = (headers = {}, active = true, emoji = '👍') => app.inject({ method: 'PUT', url: `${base}/reactions`, headers, payload: { messageId: message.id, emoji, active } })
assert.equal((await react()).statusCode, 200)
assert.equal((await react()).json().reactions[message.id][0].count, 1)
assert.equal((await react({ 'x-user': colleague })).json().reactions[message.id][0].count, 2)
const removed = await react({}, false)
assert.deepEqual(removed.json().reactions[message.id], [{ emoji: '👍', count: 1, own: false }])
assert.equal((await react({}, true, '