// Tests der Benachrichtigung (kern/dienste/benachrichtigung.ts, 5.5.0): // Was an Discord und Telegram geht (Form laut deren Doku), welche Kanäle // eingerichtet sind, und dass ein Fehlschlag gesagt wird, aber nichts // kippt. import { describe, expect, it } from 'vitest' import type { KernNachricht } from '../src/gemeinsam/nachrichten' import type { Protokoll } from '../src/gemeinsam/protokoll' import { BenachrichtigungsDienst, discordAnfrage, kanaeleAus, kanaeleEingerichtet, telegramAnfrage } from '../src/kern/dienste/benachrichtigung' import type { EinstellungsDienst } from '../src/kern/dienste/einstellungen' import type { KernKontext } from '../src/kern/kontext' function fakeKontext() { const fehler: string[] = [] const hinweise: string[] = [] const zeilen: string[] = [] const nachrichten: KernNachricht[] = [] const kontext = { db: null, dbStatus: { ok: false, fehler: 'Test' }, anFenster: (n: KernNachricht) => nachrichten.push(n), anHauptUndFenster: (n: KernNachricht) => nachrichten.push(n), hinweis: (t: string) => hinweise.push(t), fehler: (t: string) => fehler.push(t), protokoll: { schreiben: (t: string) => zeilen.push(t) } as unknown as Protokoll, smoke: true, } as unknown as KernKontext return { kontext, fehler, hinweise, zeilen, nachrichten } } function fakeEinstellungen(werte: Record): EinstellungsDienst { return { lesen: (s: string, vorgabe = '') => werte[s] ?? vorgabe } as unknown as EinstellungsDienst } describe('discordAnfrage / telegramAnfrage', () => { it('Discord: POST an den Webhook mit { content, username } — Titel fett, dann Text', () => { const a = discordAnfrage('https://discord.com/api/webhooks/1/abc', 'Rip fertig', 'Akira (1988)') expect(a.url).toBe('https://discord.com/api/webhooks/1/abc') const body = JSON.parse(a.body) as { content: string; username: string } expect(body.content).toBe('**Rip fertig**\nAkira (1988)') expect(body.username).toBe('Rippy') }) it('Discord: content bleibt unter 2000 Zeichen', () => { const a = discordAnfrage('https://x', 'T', 'x'.repeat(5000)) expect((JSON.parse(a.body) as { content: string }).content.length).toBeLessThanOrEqual(2000) }) it('Telegram: bot/sendMessage mit { chat_id, text }', () => { const a = telegramAnfrage('123:ABC', '42', 'Rip fertig', 'Akira') expect(a.url).toBe('https://api.telegram.org/bot123:ABC/sendMessage') expect(JSON.parse(a.body)).toEqual({ chat_id: '42', text: 'Rip fertig\nAkira' }) }) }) describe('kanaeleAus / kanaeleEingerichtet', () => { it('trimmt und erkennt: Discord allein reicht, Telegram nur mit Token UND Chat-ID', () => { expect(kanaeleEingerichtet(kanaeleAus((s) => (s === 'discordWebhook' ? ' https://d ' : '')))).toBe(true) expect(kanaeleEingerichtet(kanaeleAus((s) => (s === 'telegramToken' ? 't' : '')))).toBe(false) expect(kanaeleEingerichtet(kanaeleAus((s) => (s === 'telegramToken' ? 't' : s === 'telegramChatId' ? '1' : '')))).toBe(true) expect(kanaeleEingerichtet(kanaeleAus(() => ''))).toBe(false) }) }) describe('BenachrichtigungsDienst', () => { it('schickt über jeden eingerichteten Kanal und schreibt ins Protokoll', async () => { const { kontext, fehler, zeilen } = fakeKontext() const aufrufe: Array<{ url: string; body: string }> = [] const holen = (async (url: string | URL | Request, init?: RequestInit) => { aufrufe.push({ url: String(url), body: String(init?.body) }) return { status: 204 } }) as unknown as typeof fetch const d = new BenachrichtigungsDienst(kontext, fakeEinstellungen({ discordWebhook: 'https://d', telegramToken: 't', telegramChatId: '9' }), holen) expect(await d.senden('Titel', 'Text')).toEqual([]) expect(aufrufe.map((a) => a.url)).toEqual(['https://d', 'https://api.telegram.org/bott/sendMessage']) expect(fehler).toEqual([]) expect(zeilen[0]).toContain('Benachrichtigt (Discord, Telegram): Titel') }) it('ein HTTP-Fehler wird gesagt (kern-fehler), aber nicht geworfen', async () => { const { kontext, fehler } = fakeKontext() const holen = (async () => ({ status: 404 })) as unknown as typeof fetch const d = new BenachrichtigungsDienst(kontext, fakeEinstellungen({ discordWebhook: 'https://d' }), holen) expect(await d.senden('T', 'x')).toEqual(['Discord: HTTP 404']) expect(fehler).toEqual(['Benachrichtigung Discord: HTTP 404']) }) it('ein Netzfehler ebenso', async () => { const { kontext } = fakeKontext() const holen = (async () => { throw new Error('fetch failed') }) as unknown as typeof fetch const d = new BenachrichtigungsDienst(kontext, fakeEinstellungen({ discordWebhook: 'https://d' }), holen) expect(await d.senden('T', 'x')).toEqual(['Discord: fetch failed']) }) it('ausgeschaltet: nichts geht raus — außer erzwungen (Testnachricht)', async () => { const { kontext } = fakeKontext() let aufrufe = 0 const holen = (async () => { aufrufe += 1 return { status: 200 } }) as unknown as typeof fetch const d = new BenachrichtigungsDienst(kontext, fakeEinstellungen({ benachrichtigungen: 'aus', discordWebhook: 'https://d' }), holen) expect(await d.senden('T', 'x')).toEqual([]) expect(aufrufe).toBe(0) expect(await d.senden('T', 'x', true)).toEqual([]) expect(aufrufe).toBe(1) }) it('testen ohne Kanal: sagt, was fehlt; mit Kanal: „Testnachricht verschickt."', async () => { const ohne = fakeKontext() await new BenachrichtigungsDienst(ohne.kontext, fakeEinstellungen({}), (async () => ({ status: 200 })) as unknown as typeof fetch).testen() expect(ohne.fehler[0]).toContain('kein Kanal eingerichtet') const mit = fakeKontext() await new BenachrichtigungsDienst(mit.kontext, fakeEinstellungen({ discordWebhook: 'https://d' }), (async () => ({ status: 200 })) as unknown as typeof fetch).testen() expect(mit.hinweise).toEqual(['Testnachricht verschickt.']) }) })