Commander-Wünsche vom 02.09.2026 vor der Freigabe von 5.4.0 (die deshalb nie veröffentlicht wurde): Kern: kern/index.ts ist nur noch Bootstrap und Router; Dienste in kern/dienste/ (Einstellungen, Werkzeuge, Laufwerke, Erkennung, Vorgänge, Speicher, Benachrichtigung, Automatik), Kontext in kern/kontext.ts, Protokoll-Datei in gemeinsam/protokoll.ts, Platz-Rechnung in gemeinsam/platz.ts. Oberfläche: fenster/App.tsx ist nur noch der Rahmen; bausteine.tsx, dialoge/, uebersicht/ (LaufwerkKachel, KompressionsKarte, SpeicherKarte, VerlaufKarte), einstellungen/ (Karten je Funktion, SprachAuswahl), einrichtung/ (ErstEinrichtung, PosterHintergrund). Neu: Bibliothek weg, dafür Verlauf; Speicher-und-Bilanz-Karte (statfs); LED-Fußleiste; MakeMKV-Kopf in die Einstellungen; Sprach-Chips mit Dropdown statt Allgemeinem Preset; Karten Automatik/Benachrichtigung/Allgemein/Update; Durchsuchen bei Werkzeugen; Arbeitsordner lokal (Encode dort, dann in die Ablage); Platz-Prüfung vor dem Rip (Dialog und Kern); Automatik mit Countdown; Serien-Merker über Discs; Rettungs-Abbild mit Nullen nur nach gesehenen Lesefehlern (kern/rip/rettung.ts, Phase rettet, iso:-Quelle); Warteschlange pausieren; Discord/Telegram-Benachrichtigung; Protokoll-Datei je Tag; Fensterlage 80 % mittig, gemerkt; Erst-Einrichtung in acht Schritten vor dem Poster-Grid. Beweise: 413 Tests (neu: speicher, benachrichtigung, automatik, rettung, fensterlage, protokoll, kleinkram-550), drei Typprüfungen, Klick-Beweis mit 29 Schritten inklusive Erst-Einrichtung (zweiter Start mit RIPPY_START_BEREICH=einrichtung), Bilder beweise/klick/01…09. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
113 lines
5.9 KiB
TypeScript
113 lines
5.9 KiB
TypeScript
// 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<string, string>): 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<TOKEN>/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.'])
|
|
})
|
|
})
|