Files
rippy/rippy-windows/test/rohaufraeumen.test.ts
T
HitonabiandClaude Fable 5.1 3f7f962dff feat(v5): Kern 5.4.0 — Vorgaenge, Kompressions-Warteschlange, Roh-Aufraeumen, Sprachen je Titel, Erkennung v2
WAS: Der Rip endet, sobald die Roh-Dateien liegen; die Kompression laeuft
danach in einer Warteschlange (eine zur Zeit), das Laufwerk ist frei fuer
die naechste Disc. Jeder Rip ist jetzt ein Datensatz (Tabelle vorgaenge,
Schema 4): Roh-Ordner, Zielordner, Phase, Auftrag als JSON. Darauf bauen:
Roh-Aufraeumen nach Regel (Vorgabe 7 Tage, nur bei gelungener Kompression
und nur wenn jede fertige Datei da und nicht leer ist), Wiederholen ab
Kompression, Aufraeumen halber Dateien nach Abbruch/Fehler, Wiederaufnahme
nach Neustart. Unkomprimierte Rips (4K, kein HandBrake) werden in den
Zielordner VERSCHOBEN statt unter roh\ liegen zu bleiben.
Sprachen je Titel reisen vom Info-Lauf (SINFO) bis in HandBrake; fremder
Ton schaltet den deutschen Untertitel vor (--subtitle-default=1). Vor der
Kompression prueft ein HandBrake-Scan die Roh-Datei. MakeMKVs
Auswahlregel wird gesetzt, wenn keine steht, damit alle Sprachen in die
Roh-Datei kommen. Staffel und erste Folge von Hand schlagen die
Laufzeit-Raterei. Durchgehender Fortschritt, nach Groesse gewichtet.
Erkennung v2: leere TMDb-Eintraege (Stubs) zaehlen nicht, Laufzeit wird
nach dem Info-Lauf gegen TMDb gehalten, Titel-Struktur als Fingerabdruck,
bdmt_deu als zweite Titel-Variante. TMDb liefert Logo, Tagline,
Bewertung, FSK und Besetzung in derselben Anfrage. Laufwerks-Gesundheit
aus dem Windows-Ereignisprotokoll (wevtutil): Disc oder Hardware.

WARUM: Uebergabe-Punkte 1-5, 8-12 des SAVEPOINT vom 01.09.2026 — vom
Commander gutgeheissen (7 Tage / Untertitel vorgewaehlt / entkoppelte
Kompression waren seine Wahl).

GEMESSEN (Regel D): HandBrake 1.11 Sprach-Schalter an einer ffmpeg-
Testdatei (jpn/deu/deu/eng + deu/eng), HandBrake --scan --json Format,
TMDb movie/149 + tv/46296 mit append_to_response, wevtutil /f:xml an
diesem PC (338x cdrom 7, 8x cdrom 11 in 30 Tagen), makemkvcon --minlength,
MakeMKV-Auswahlregel laut forum.makemkv.com t=4386.

348 Tests gruen (vorher 261), Typpruefung und Bau gruen. Oberflaeche
folgt im naechsten Schritt.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-01 22:02:03 +02:00

149 lines
6.6 KiB
TypeScript

// Tests des Roh-Aufräumens (kern/ablage/rohaufraeumen.ts, 5.4.0): die
// Regel, die Frist, die drei Sicherungen — und dass fremde Ordner nur
// gezeigt, nie von selbst gelöscht werden.
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, describe, expect, it } from 'vitest'
import {
aufraeumen,
faelligAm,
fremdeOrdner,
istUnterRohWurzel,
loeschHindernis,
ordnerGroesse,
regelLesen,
regelText,
type AufraeumKandidat,
type AufraeumZugriff,
} from '../src/kern/ablage/rohaufraeumen'
let wegwerf: string[] = []
afterEach(() => {
for (const o of wegwerf.splice(0)) rmSync(o, { recursive: true, force: true })
})
describe('regelLesen', () => {
it('kennt behalten, sofort und Tage — und die Vorgabe 7', () => {
expect(regelLesen('behalten')).toEqual({ art: 'behalten' })
expect(regelLesen('sofort')).toEqual({ art: 'sofort' })
expect(regelLesen('30')).toEqual({ art: 'tage', tage: 30 })
expect(regelLesen(null)).toEqual({ art: 'tage', tage: 7 })
expect(regelLesen('')).toEqual({ art: 'tage', tage: 7 })
expect(regelLesen('quatsch')).toEqual({ art: 'tage', tage: 7 })
expect(regelLesen('-3')).toEqual({ art: 'tage', tage: 7 })
expect(regelText(regelLesen('7'))).toContain('7 Tage')
})
})
describe('faelligAm', () => {
it('Tage nach dem Ende; sofort = am Ende; behalten = nie', () => {
expect(faelligAm('2026-09-01T20:00:00.000Z', { art: 'tage', tage: 7 })).toBe('2026-09-08T20:00:00.000Z')
expect(faelligAm('2026-09-01T20:00:00.000Z', { art: 'sofort' })).toBe('2026-09-01T20:00:00.000Z')
expect(faelligAm('2026-09-01T20:00:00.000Z', { art: 'behalten' })).toBe('')
expect(faelligAm(null, { art: 'sofort' })).toBe('')
expect(faelligAm('kaputt', { art: 'sofort' })).toBe('')
})
})
const fertigeDateien = ['E:\\Rippy\\Filme\\Akira (1988)\\title_t00.mkv']
const kandidat = (teil: Partial<AufraeumKandidat> = {}): AufraeumKandidat => ({
id: 'bluray-1',
phase: 'fertig',
komprimiert: true,
rohOrdner: 'E:\\Rippy\\roh\\bluray-1',
rohGeloeschtAm: null,
beendet: '2026-09-01T20:00:00.000Z',
fertigeDateien,
...teil,
})
const zugriff = (überschreiben: Partial<AufraeumZugriff> = {}): AufraeumZugriff & { geloescht: string[] } => {
const geloescht: string[] = []
return {
existiert: () => true,
groesse: () => 1000,
ordnerLoeschen: (p) => {
geloescht.push(p)
},
geloescht,
...überschreiben,
}
}
const regel7 = { art: 'tage' as const, tage: 7 }
const spaeter = new Date('2026-09-10T00:00:00Z')
const frueher = new Date('2026-09-03T00:00:00Z')
describe('loeschHindernis — die drei Sicherungen', () => {
it('fertig, komprimiert, Ergebnis da, Frist um: löschbar', () => {
expect(loeschHindernis(kandidat(), regel7, spaeter, zugriff())).toBe('')
})
it('vor der Frist: nicht löschbar, mit Datum', () => {
expect(loeschHindernis(kandidat(), regel7, frueher, zugriff())).toContain('Frist läuft bis 2026-09-08')
})
it('nicht fertig / nicht komprimiert / schon gelöscht / Regel behalten', () => {
expect(loeschHindernis(kandidat({ phase: 'fehler' }), regel7, spaeter, zugriff())).toContain('nicht fertig')
expect(loeschHindernis(kandidat({ komprimiert: false }), regel7, spaeter, zugriff())).toContain('nicht komprimiert')
expect(loeschHindernis(kandidat({ rohGeloeschtAm: 'x' }), regel7, spaeter, zugriff())).toBe('schon gelöscht')
expect(loeschHindernis(kandidat(), { art: 'behalten' }, spaeter, zugriff())).toBe('Regel: behalten')
})
it('die fertige Datei fehlt oder ist leer: NIE löschen', () => {
expect(loeschHindernis(kandidat(), regel7, spaeter, zugriff({ existiert: (p) => p.endsWith('bluray-1') }))).toContain('fertige Datei fehlt')
expect(loeschHindernis(kandidat(), regel7, spaeter, zugriff({ groesse: () => 0 }))).toContain('ist leer')
expect(loeschHindernis(kandidat({ fertigeDateien: [] }), regel7, spaeter, zugriff())).toContain('keine fertige Datei')
})
it('sofort: gleich nach dem Ende löschbar', () => {
expect(loeschHindernis(kandidat(), { art: 'sofort' }, new Date('2026-09-01T20:00:01Z'), zugriff())).toBe('')
})
})
describe('aufraeumen', () => {
it('löscht nur die Fälligen und sagt, warum der Rest blieb', () => {
const z = zugriff()
const ergebnis = aufraeumen(
[kandidat(), kandidat({ id: 'jung', beendet: '2026-09-09T00:00:00.000Z' }), kandidat({ id: 'roh', komprimiert: false })],
regel7,
spaeter,
z,
)
expect(ergebnis.geloescht).toEqual(['bluray-1'])
expect(z.geloescht).toEqual(['E:\\Rippy\\roh\\bluray-1'])
expect(ergebnis.uebersprungen.map((u) => u.id)).toEqual(['jung', 'roh'])
expect(ergebnis.fehler).toEqual([])
})
it('ein Ordner, der sich nicht löschen lässt, ist ein lauter Fehler, kein stilles Nichts', () => {
const ergebnis = aufraeumen([kandidat()], regel7, spaeter, zugriff({ ordnerLoeschen: () => { throw new Error('EBUSY') } }))
expect(ergebnis.geloescht).toEqual([])
expect(ergebnis.fehler[0].grund).toContain('EBUSY')
})
})
describe('Belegung und fremde Ordner — am echten Dateisystem', () => {
it('ordnerGroesse zählt rekursiv; fremdeOrdner kennt die Vorgänge und lässt sie aus', () => {
const wurzel = mkdtempSync(join(tmpdir(), 'rippy-roh-'))
wegwerf.push(wurzel)
const roh = join(wurzel, 'roh')
mkdirSync(join(roh, 'bluray-1', 'tief'), { recursive: true })
mkdirSync(join(roh, 'alt-vor-540'), { recursive: true })
writeFileSync(join(roh, 'bluray-1', 'a.mkv'), 'x'.repeat(100))
writeFileSync(join(roh, 'bluray-1', 'tief', 'b.mkv'), 'x'.repeat(50))
writeFileSync(join(roh, 'alt-vor-540', 'c.mkv'), 'x'.repeat(7))
writeFileSync(join(roh, 'lose.txt'), 'x')
expect(ordnerGroesse(join(roh, 'bluray-1'))).toBe(150)
expect(ordnerGroesse(join(roh, 'gibt-es-nicht'))).toBe(0)
const fremde = fremdeOrdner(roh, new Set([join(roh, 'bluray-1')]))
expect(fremde).toEqual([{ ordner: join(roh, 'alt-vor-540'), bytes: 7 }])
expect(fremdeOrdner(join(wurzel, 'nix'), new Set())).toEqual([])
})
})
describe('istUnterRohWurzel — nur Ordner DIREKT unter roh\\', () => {
it('erlaubt genau eine Ebene', () => {
expect(istUnterRohWurzel('E:\\Rippy\\roh', 'E:\\Rippy\\roh\\bluray-1')).toBe(true)
expect(istUnterRohWurzel('E:\\Rippy\\roh\\', 'e:\\rippy\\roh\\bluray-1\\')).toBe(true)
expect(istUnterRohWurzel('E:\\Rippy\\roh', 'E:\\Rippy\\roh')).toBe(false)
expect(istUnterRohWurzel('E:\\Rippy\\roh', 'E:\\Rippy\\roh\\a\\b')).toBe(false)
expect(istUnterRohWurzel('E:\\Rippy\\roh', 'E:\\Rippy\\Filme\\Akira')).toBe(false)
expect(istUnterRohWurzel('E:\\Rippy\\roh', 'E:\\Rippy\\rohling\\x')).toBe(false)
})
})