import { test, before, after } from 'node:test' import assert from 'node:assert/strict' import { createHmac } from 'node:crypto' import fs from 'node:fs/promises' import os from 'node:os' import path from 'node:path' import express from 'express' import { startFakeGitea } from './helpers/fakeGitea.js' import { startFakeAppwrite } from './helpers/fakeAppwrite.js' import { mailSink } from './support/mailSink.js' const WEBHOOK_TOKEN = 's3cret' const REPO = 'WEBklar/amin-friseur' let gitea let aw let tmp let originalCwd let server let baseUrl let setMailTransportForTests function pushPayload({ sha, pusher }) { return { ref: 'refs/heads/main', repository: { full_name: REPO, name: 'amin-friseur', html_url: `${gitea.baseUrl}/${REPO}` }, commits: [{ id: sha, message: `Commit ${sha}`, author: { name: pusher }, url: `${gitea.baseUrl}/${REPO}/commit/${sha}` }], pusher: { username: pusher }, after: sha, } } async function webhook(payload) { const bodyText = JSON.stringify(payload) const signature = createHmac('sha256', WEBHOOK_TOKEN).update(bodyText).digest('hex') const res = await fetch(`${baseUrl}/webhook/gitea`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Gitea-Signature': signature }, body: bodyText, }) return { status: res.status, body: await res.json().catch(() => null) } } /** Die Mail geht ohne await auf die Webhook-Antwort raus - kurz warten, bis sie da ist. */ async function waitFor(check, ms = 1000) { const end = Date.now() + ms while (Date.now() < end) { if (check()) return true await new Promise((r) => setTimeout(r, 10)) } return check() } before(async () => { gitea = await startFakeGitea({ repos: [{ full_name: REPO }] }) aw = await startFakeAppwrite({ seed: { websiteProjects: [{ $id: 'p1', repoFullName: REPO, projectName: 'amin-friseur', customerId: 'c1', ticketId: 't1' }], workorders: [{ $id: 't1', woid: '100118', customerName: 'Amin Friseur', topic: 'Website', status: 'Open' }], customers: [{ $id: 'c1', name: 'Amin Friseur' }], employees: [{ $id: 'k', displayName: 'Kenso Grimm', shortcode: 'KNSO', email: 'kenso@webklar.com' }], }, }) originalCwd = process.cwd() tmp = await fs.mkdtemp(path.join(os.tmpdir(), 'push-zeitmail-')) process.chdir(tmp) Object.assign(process.env, { GITEA_BASE_URL: gitea.baseUrl, GITEA_API_TOKEN: 'test-token', GITEA_WEBHOOK_TOKEN: WEBHOOK_TOKEN, APPWRITE_ENDPOINT: aw.endpoint, APPWRITE_API_KEY: 'test-key', PREVIEW_DEPLOY_ROOT: '', TRAEFIK_DYNAMIC_DIR: '', PUSH_TIME_SECRET: 'w'.repeat(40), }) const [{ default: giteaWebhookRoutes }, mailer] = await Promise.all([ import('../server/routes/webhook/gitea.js'), import('../server/services/mailer.js'), ]) setMailTransportForTests = mailer.setMailTransportForTests const app = express() app.use(express.json({ limit: '2mb', verify: (req, _res, buf) => { req.rawBody = buf } })) app.use('/webhook', giteaWebhookRoutes) server = await new Promise((resolve) => { const s = app.listen(0, '127.0.0.1', () => resolve(s)) }) baseUrl = `http://127.0.0.1:${server.address().port}` }) after(async () => { setMailTransportForTests?.(null) if (server) await new Promise((resolve) => server.close(resolve)) if (originalCwd) process.chdir(originalCwd) await gitea?.close() await aw?.close() if (tmp) await fs.rm(tmp, { recursive: true, force: true }) }) test('Push von knso → eine Zeitmail, Antwort ohne Dokumente', async () => { const sink = mailSink() setMailTransportForTests(sink) const r = await webhook(pushPayload({ sha: 'aaaa1111', pusher: 'knso' })) assert.equal(r.status, 200) assert.equal(r.body.gitWorksheet.created, true) assert.equal(r.body.gitWorksheet.ticket, undefined) assert.equal(r.body.gitWorksheet.worksheet, undefined) assert.ok(r.body.gitWorksheet.worksheetId) assert.ok(await waitFor(() => sink.sent.length === 1)) assert.deepEqual(sink.sent[0].to.map((t) => t.address), ['kenso@webklar.com']) assert.match(sink.sent[0].subject, /^\[Zeit\] #100118 Amin Friseur/) assert.match(sink.sent[0].html, new RegExp(`/zeit/[^"?]+\\?min=60`)) }) test('derselbe Push erneut → keine zweite Mail', async () => { const sink = mailSink() setMailTransportForTests(sink) const r = await webhook(pushPayload({ sha: 'aaaa1111', pusher: 'knso' })) assert.equal(r.status, 200) assert.equal(r.body.gitWorksheet.reason, 'duplicate_commit') await new Promise((res) => setTimeout(res, 100)) assert.equal(sink.sent.length, 0) }) test('System-Konto → keine Mail', async () => { const sink = mailSink() setMailTransportForTests(sink) const r = await webhook(pushPayload({ sha: 'bbbb2222', pusher: 'webklar-system' })) assert.equal(r.status, 200) await new Promise((res) => setTimeout(res, 100)) assert.equal(sink.sent.length, 0) }) test('Mailfehler ändert die Webhook-Antwort nicht', async () => { setMailTransportForTests({ sendMail: async () => { throw new Error('SMTP kaputt') } }) const r = await webhook(pushPayload({ sha: 'cccc3333', pusher: 'knso' })) assert.equal(r.status, 200) assert.equal(r.body.gitWorksheet.created, true) })