Files
rippy/rippy-windows/test/benachrichtigung.test.ts
T
HitonabiandClaude Fable 5.1 3dc6798c79 feat(v5): 5.5.0 — Kern und Oberfläche aufgeteilt, Übersicht und Einstellungen neu, Erst-Einrichtung mit Poster-Kulisse
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>
2026-09-02 17:51:15 +02:00

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.'])
})
})