feat(windows): Arbeitsordner im Setup, Beta-Key auf Knopfdruck
Ampel / ampel (push) Successful in 1m18s

Zwei Wuensche des Commanders vom 28.08.2026.

## 1. „kannst du noch einbauen das man den arbeitsordner usw. in den
##     Einstellungen setzen kann und direkt im setup?"

In den Einstellungen ging es seit 35370fd (freies Pfadfeld plus die Laufwerke
als Ein-Klick-Wahl). Im SETUP wurde bisher nur nach Programmordner und Ablage
gefragt — der Arbeitsordner tauchte erst auf, wenn schon installiert war.

Das ist die falsche Reihenfolge: Der Roh-Rip einer 4K-UHD ist bis zu 100 GB
gross. Wer erst NACH der ersten vollen Platte erfaehrt, wo die liegen, hat es
zu spaet erfahren — genau so lief am 25.07.2026 die VM-Platte voll.

Neu ist auch ein Waechter, der alle drei Arten von halb angeschlossenem Feld
faengt (kein Eingabefeld, keine Vorbelegung, wird beim Installieren nicht
mitgeschickt). Der dritte Fall ist der gemeinste: Man waehlt etwas, es
passiert nichts, und niemand sagt warum.

## 2. „der könnte theoretisch auch automatisch ausgelesen werden, ich glaube
##     das web rippy kann das"

Er hatte recht. `makemkv_key.refresh_loop()` laeuft seit jeher taeglich mit
(main.py, Startereignis). GEMESSEN am 28.08.2026: Forum antwortet in 3,3 s,
gueltiger Key mit 62 Zeichen, und in seinen Einstellungen lag bereits einer.

Es war nur nichts davon zu sehen. Kein Knopf, keine Meldung, keine Zeitangabe
— im Feld stand ein Key, und ob der von Hand kam oder von selbst, wusste
niemand. Eine Automatik, die man nicht sehen kann, ist fuer den Benutzer
keine.

Dazu kam eine echte Luecke: Die Schleife startet 60 Sekunden nach dem Server.
Wer gleich nach dem Einrichten eine Blu-ray einlegt, rippt ohne Key — MakeMKV
faellt in den 30-Tage-Testmodus. Das Setup holt ihn jetzt selbst.

Der geholte Key wandert NICHT ueber die Leitung zurueck: Er steht in den
Einstellungen, und ein zweiter Weg zu demselben Wert ist ein zweiter Weg, ihn
zu verlieren.

Rechtlich unveraendert: oeffentliche Beta-LIZENZ der Software, KEIN
Disc-Schluessel. Rippy liefert und verteilt keine Disc-Schluessel.

803 Tests gruen, ruff sauber.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-08-29 11:53:12 +02:00
co-authored by Claude Opus 5
parent d0281f7a1b
commit 678fe276f9
7 changed files with 372 additions and 13 deletions
+49
View File
@@ -2406,6 +2406,55 @@ async def get_keydb_status():
return await asyncio.to_thread(sammle) return await asyncio.to_thread(sammle)
@app.post("/system/makemkv-key/holen")
async def makemkv_key_holen():
"""Holt den kostenlosen MakeMKV-Beta-Key JETZT aus dem Forum.
## Warum es diesen Knopf gibt (Commander 28.08.2026)
> „Bezüglich des MKV Beta Keys — der könnte theoretisch auch automatisch
> ausgelesen werden, ich glaube das web rippy kann das"
Er hatte recht: `makemkv_key.refresh_loop()` läuft seit jeher täglich mit
(main.py, Startereignis). Nachgemessen: Forum antwortet in 3,3 Sekunden,
Key mit 62 Zeichen.
Nur war davon **nichts zu sehen**. Kein Knopf, keine Zeitangabe, kein
Hinweis — im Einstellungsfeld stand ein Key, und ob der von Hand kam oder
von selbst, wusste niemand. Eine Automatik, die man nicht sehen kann, ist
für den Benutzer keine.
Der Key wechselt etwa monatlich. Wer nicht warten will, bis die Schleife
das nächste Mal nachsieht, drückt hier.
Rechtlich unverändert: öffentliche Beta-LIZENZ der Software, KEIN
Disc-Schlüssel.
"""
import makemkv_key as key_modul
try:
key = await asyncio.to_thread(key_modul.fetch_current_key)
except Exception as e: # noqa: BLE001
raise HTTPException(
status_code=502,
detail=f"Das MakeMKV-Forum antwortet nicht ({type(e).__name__}). "
"Der Key lässt sich weiter von Hand eintragen.")
if not key:
raise HTTPException(
status_code=502,
detail="Im Forum-Beitrag stand kein Key im erwarteten Format. "
"Vermutlich hat sich die Seite geändert.")
geaendert = await asyncio.to_thread(key_modul._apply_key, key)
await asyncio.to_thread(
db.add_log, "success", "makemkv-key",
"Beta-Key aus dem Forum geholt (…%s)%s"
% (key[-6:], "" if geaendert else " — war schon der aktuelle"))
# Der Key selbst wird NICHT zurueckgegeben: Er steht in den Einstellungen,
# und ein zweiter Weg zu demselben Wert ist ein zweiter Weg, ihn zu
# verlieren.
return {"geholt": True, "geaendert": geaendert, "endet_auf": key[-6:]}
@app.post("/system/keydb") @app.post("/system/keydb")
async def set_keydb(request: KeydbRequest): async def set_keydb(request: KeydbRequest):
"""Legt die vom Nutzer mitgebrachte KEYDB.cfg ab (atomar, ersetzt die alte). """Legt die vom Nutzer mitgebrachte KEYDB.cfg ab (atomar, ersetzt die alte).
+70
View File
@@ -1,4 +1,6 @@
"""Tests für den MakeMKV-Beta-Key-Parser (dependency-frei, nur extract_key).""" """Tests für den MakeMKV-Beta-Key-Parser (dependency-frei, nur extract_key)."""
import pytest
from makemkv_key import extract_key from makemkv_key import extract_key
# Beispiel-Key im echten Format (T- + 64 Zeichen [A-Za-z0-9_]). # Beispiel-Key im echten Format (T- + 64 Zeichen [A-Za-z0-9_]).
@@ -26,3 +28,71 @@ def test_extract_key_liefert_ganzen_key():
treffer = extract_key(f"Vorher-Text {_KEY} Nachher-Text") treffer = extract_key(f"Vorher-Text {_KEY} Nachher-Text")
assert treffer == _KEY assert treffer == _KEY
assert treffer.startswith("T-") assert treffer.startswith("T-")
# ── Der Knopf „Jetzt aus dem Forum holen" (Commander 28.08.2026) ─────────
#
# > „Bezüglich des MKV Beta Keys - der könnte theoretisch auch automatisch
# > ausgelesen werden, ich glaube das web rippy kann das"
#
# Er hatte recht: `refresh_loop()` laeuft seit jeher taeglich mit (main.py,
# Startereignis). Gemessen am 28.08.2026: Forum antwortet in 3,3 s, Key mit 62
# Zeichen. Nur war davon NICHTS zu sehen — kein Knopf, keine Meldung. Eine
# Automatik, die man nicht sehen kann, ist fuer den Benutzer keine.
@pytest.fixture
def klient():
from fastapi.testclient import TestClient
from main import app
return TestClient(app)
def test_der_knopf_holt_und_meldet_was_passiert_ist(klient, monkeypatch):
import main as api
monkeypatch.setattr("makemkv_key.fetch_current_key", lambda *a, **k: _KEY)
monkeypatch.setattr("makemkv_key._apply_key", lambda key: True)
monkeypatch.setattr(api.db, "add_log", lambda *a, **k: None)
antwort = klient.post("/system/makemkv-key/holen")
assert antwort.status_code == 200
daten = antwort.json()
assert daten["geholt"] is True
assert daten["geaendert"] is True
assert daten["endet_auf"] == _KEY[-6:]
def test_der_key_selbst_wandert_NICHT_ueber_die_leitung(klient, monkeypatch):
"""Er steht in den Einstellungen. Ein zweiter Weg zu demselben Wert ist
ein zweiter Weg, ihn zu verlieren."""
import main as api
monkeypatch.setattr("makemkv_key.fetch_current_key", lambda *a, **k: _KEY)
monkeypatch.setattr("makemkv_key._apply_key", lambda key: False)
monkeypatch.setattr(api.db, "add_log", lambda *a, **k: None)
text = klient.post("/system/makemkv-key/holen").text
assert _KEY not in text
assert _KEY[:20] not in text
def test_ein_stummes_forum_ist_ein_klarer_fehler_kein_leerer_erfolg(klient,
monkeypatch):
"""Sonst hiesse „geholt: true" mit leerem Key, es sei alles in Ordnung."""
def wirft(*a, **k):
raise OSError("Verbindung abgelehnt")
monkeypatch.setattr("makemkv_key.fetch_current_key", wirft)
antwort = klient.post("/system/makemkv-key/holen")
assert antwort.status_code == 502
assert "Forum" in antwort.json()["detail"]
def test_geaendertes_seitenformat_wird_benannt(klient, monkeypatch):
"""Kein Treffer heisst NICHT „kein Key noetig" — es heisst, die Seite hat
sich geaendert. Das muss dastehen, sonst sucht niemand danach."""
monkeypatch.setattr("makemkv_key.fetch_current_key", lambda *a, **k: None)
antwort = klient.post("/system/makemkv-key/holen")
assert antwort.status_code == 502
assert "geändert" in antwort.json()["detail"]
+69 -7
View File
@@ -213,6 +213,9 @@ export default function SettingsPage() {
// Ablageziele unter /app/media inkl. eingehängter Freigaben — speist die // Ablageziele unter /app/media inkl. eingehängter Freigaben — speist die
// Auswahl des Arbeitsverzeichnisses (vorher musste man den Pfad tippen). // Auswahl des Arbeitsverzeichnisses (vorher musste man den Pfad tippen).
const [ziele, setZiele] = useState<StorageZiel[]>([]) const [ziele, setZiele] = useState<StorageZiel[]>([])
// Beta-Key: läuft gerade ein Abruf, und was kam zuletzt dabei heraus?
const [keyHolt, setKeyHolt] = useState(false)
const [keyMeldung, setKeyMeldung] = useState('')
const [keystore, setKeystore] = useState<KeystoreStatus | null>(null) const [keystore, setKeystore] = useState<KeystoreStatus | null>(null)
const [keystoreBusy, setKeystoreBusy] = useState(false) const [keystoreBusy, setKeystoreBusy] = useState(false)
const keystoreInput = useRef<HTMLInputElement>(null) const keystoreInput = useRef<HTMLInputElement>(null)
@@ -253,6 +256,34 @@ export default function SettingsPage() {
api.get('/capabilities').then(r => setWorkers(r.data.workers || [])).catch(() => setWorkers([])) api.get('/capabilities').then(r => setWorkers(r.data.workers || [])).catch(() => setWorkers([]))
} }
/*
* Den Beta-Key JETZT holen, statt auf die tägliche Schleife zu warten.
*
* Der geholte Key wird nicht angezeigt — er wandert in die Einstellungen,
* und die werden hier ohnehin neu geladen. Zwei Wege zu demselben Wert sind
* zwei Wege, ihn zu verlieren.
*/
const keyJetztHolen = async () => {
setKeyHolt(true)
setKeyMeldung('')
try {
const r = await api.post('/system/makemkv-key/holen')
const antwort = await api.get('/settings')
setSettings({ ...defaultSettings, ...antwort.data })
setKeyMeldung(r.data?.geaendert
? `Neuer Key geholt (endet auf …${r.data.endet_auf}).`
: `Schon aktuell (endet auf …${r.data?.endet_auf || '?'}).`)
toast('success', 'MakeMKV-Beta-Key aus dem Forum geholt.')
} catch (e: any) {
const grund = e?.response?.data?.detail
|| 'Das Forum ist nicht erreichbar.'
setKeyMeldung('Fehlgeschlagen: ' + grund)
toast('error', grund)
} finally {
setKeyHolt(false)
}
}
const keydbHochladen = async (datei: File) => { const keydbHochladen = async (datei: File) => {
setKeydbBusy(true) setKeydbBusy(true)
try { try {
@@ -1295,13 +1326,44 @@ export default function SettingsPage() {
</p> </p>
</div> </div>
<Input {/*
type="password" Der Beta-Key holt sich selbst — man sah es nur nirgends.
label="MakeMKV Beta-Key"
value={settings.makemkvAppKey} Commander am 28.08.2026: „Bezüglich des MKV Beta Keys — der
onChange={(e) => handleChange('makemkvAppKey', e.target.value)} könnte theoretisch auch automatisch ausgelesen werden, ich
placeholder="T- (Forum-Thread MakeMKV is free while in beta)" glaube das web rippy kann das." Er hatte recht:
/> `makemkv_key.refresh_loop()` läuft täglich mit, seit es die
Datei gibt (gemessen: Forum antwortet in 3,3 s, Key mit 62
Zeichen). Hier stand aber nur ein Passwortfeld ob der Wert
darin von Hand kam oder von selbst, war nicht zu erkennen.
Eine Automatik, die man nicht sehen kann, ist für den
Benutzer keine.
*/}
<div className="space-y-2">
<Input
type="password"
label="MakeMKV Beta-Key"
value={settings.makemkvAppKey}
onChange={(e) => handleChange('makemkvAppKey', e.target.value)}
placeholder="T-… (Forum-Thread „MakeMKV is free while in beta”)"
/>
<div className="flex items-center gap-2 flex-wrap">
<Button
variant="secondary"
size="sm"
disabled={keyHolt}
onClick={keyJetztHolen}
>
{keyHolt ? 'wird geholt …' : 'Jetzt aus dem Forum holen'}
</Button>
<span className={`text-xs ${keyMeldung.startsWith('Fehlgeschlagen')
? 'text-red-600 dark:text-red-400'
: 'text-slate-500 dark:text-slate-400'}`}>
{keyMeldung || 'Rippy holt ihn ohnehin täglich selbst — '
+ 'der Key wechselt etwa monatlich.'}
</span>
</div>
</div>
{/* {/*
Schlüsselspeicher — der Hauptweg für 4K-UHD (Befund 25.07.2026, Schlüsselspeicher — der Hauptweg für 4K-UHD (Befund 25.07.2026,
+7
View File
@@ -403,6 +403,13 @@ def vorgaben() -> dict:
return { return {
"ziel": reg.standard_installationsordner(), "ziel": reg.standard_installationsordner(),
"ablage": betrieb.ablage_vorgabe({}, container=False, system="windows"), "ablage": betrieb.ablage_vorgabe({}, container=False, system="windows"),
# Der Arbeitsordner gehoert ins Setup, nicht erst in die Einstellungen
# (Commander-Wunsch 28.08.2026). Grund: Der Roh-Rip einer 4K-UHD ist
# bis zu 100 GB gross. Wer das erst NACH der ersten vollen Platte
# erfaehrt, hat es zu spaet erfahren — genau so lief am 25.07.2026
# die VM-Platte voll.
"arbeit": betrieb.arbeits_vorgabe({}, container=False,
plattform_name="win32"),
"port": STANDARD_PORT, "port": STANDARD_PORT,
"autostart": True, "autostart": True,
"verknuepfungen": True, "verknuepfungen": True,
+80 -3
View File
@@ -221,10 +221,18 @@ class Bruecke:
starten_danach=False, starten_danach=False,
verknuepfen=bool(optionen.get("verknuepfungen", True)), verknuepfen=bool(optionen.get("verknuepfungen", True)),
) )
orte = {}
if optionen.get("ablage"): if optionen.get("ablage"):
orte["outputDir"] = optionen["ablage"]
# Der Arbeitsordner gehoert mit ins Setup (Commander 28.08.2026).
# Leer heisst „nimm die Vorgabe" — dann steht hier nichts, und der
# Betrieb rechnet ihn sich neben der Ablage aus.
if optionen.get("arbeit"):
orte["workDir"] = optionen["arbeit"]
if orte:
self._lauf.beginne("ablage") self._lauf.beginne("ablage")
melden("Ablage wird gespeichert …") melden("Ordner werden gespeichert …")
_ablage_speichern(optionen["ablage"]) _einstellungen_schreiben(orte)
self._lauf.beginne("werkzeuge") self._lauf.beginne("werkzeuge")
if optionen.get("werkzeuge_holen", True): if optionen.get("werkzeuge_holen", True):
melden("Werkzeuge werden geprüft …") melden("Werkzeuge werden geprüft …")
@@ -235,6 +243,7 @@ class Bruecke:
aktualisieren=bool(optionen.get("aktualisieren", False))) aktualisieren=bool(optionen.get("aktualisieren", False)))
melden(werkzeug_einrichtung.bericht(bericht)) melden(werkzeug_einrichtung.bericht(bericht))
bereit = bericht["bereit"] bereit = bericht["bereit"]
_beta_key_holen(melden)
else: else:
bereit = True bereit = True
self._lauf.beginne("fertig") self._lauf.beginne("fertig")
@@ -299,16 +308,73 @@ def _ablage_speichern(pfad: str) -> None:
Der Store wird direkt benutzt. Ein Umweg über den Suchpfad der API war Der Store wird direkt benutzt. Ein Umweg über den Suchpfad der API war
ohnehin überflüssig. ohnehin überflüssig.
""" """
_einstellungen_schreiben({"outputDir": pfad})
def _einstellungen_schreiben(neu: dict) -> None:
"""Werte in die Einstellungen mischen — lesen, setzen, zurückschreiben.
`save_settings` überschreibt das GANZE JSON, deshalb erst lesen.
"""
from rippy import config, store from rippy import config, store
werte = config.laden() werte = config.laden()
store.verbinden(config.datenbank_url(werte)) store.verbinden(config.datenbank_url(werte))
store.init_db() store.init_db()
aktuell = store.get_settings() or {} aktuell = store.get_settings() or {}
aktuell["outputDir"] = pfad aktuell.update(neu)
store.save_settings(aktuell) store.save_settings(aktuell)
def _beta_key_holen(melden=None) -> str:
"""Den kostenlosen MakeMKV-Beta-Key aus dem Forum holen und ablegen.
## Warum das ins Setup gehört (Commander 28.08.2026)
> „Bezüglich des MKV Beta Keys — der könnte theoretisch auch automatisch
> ausgelesen werden, ich glaube das web rippy kann das"
Er hat recht, es kann: `docker/api/makemkv_key.py` holt den Key aus dem
Forum-Thread t=1053 und läuft seit jeher täglich mit. Nachgemessen am
28.08.2026: Antwort in 3,3 Sekunden, gültiger Key mit 62 Zeichen.
Nur half das beim ERSTEN Rip nicht: Die Schleife startet 60 Sekunden nach
dem Serverstart. Wer gleich nach dem Einrichten eine Blu-ray einlegt, rippt
ohne Key — und MakeMKV fällt in den 30-Tage-Testmodus.
Ohne Key ist Rippy nicht kaputt; DVDs gehen weiterhin. Deshalb ist ein
Fehlschlag hier auch kein Abbruch, sondern eine Zeile im Protokoll.
Rechtlich unverändert: Das ist die öffentliche Beta-LIZENZ der Software,
NICHT ein Disc-Schlüssel. Rippy liefert und verteilt keine Disc-Schlüssel.
"""
melden = melden or (lambda *a, **k: None)
try:
import sys
from rippy.daemon import _api_pfad
# `_api_pfad` gibt den Pfad NUR zurueck — eingehaengt wird er in
# daemon.starten(). Hier laeuft der Assistent, nicht der Dienst.
pfad = _api_pfad()
if pfad and pfad not in sys.path:
sys.path.insert(0, pfad)
import makemkv_key
key = makemkv_key.fetch_current_key()
except Exception as ex: # noqa: BLE001
melden(" Beta-Key nicht erreichbar (%s) — nachtragbar unter "
"Einstellungen." % type(ex).__name__)
return ""
if not key:
melden(" Kein Beta-Key im Forum gefunden — nachtragbar unter "
"Einstellungen.")
return ""
_einstellungen_schreiben({"makemkvAppKey": key})
melden(" MakeMKV-Beta-Key geholt (…%s)." % key[-6:])
return key
def starten() -> dict: def starten() -> dict:
"""Öffnet den Assistenten. Gibt zurück, was daraus geworden ist. """Öffnet den Assistenten. Gibt zurück, was daraus geworden ist.
@@ -426,6 +492,15 @@ SEITE = r"""<!DOCTYPE html>
<button onclick="waehlen('ablage')">Durchsuchen …</button> <button onclick="waehlen('ablage')">Durchsuchen …</button>
</div> </div>
</label> </label>
<label class="feld"><span>Arbeitsordner für die Rohdaten
<div class="hinweis">Hier liegt die Disc zwischendurch am Stück — bei einer
4K-UHD bis zu 100 GB. Am besten dasselbe Laufwerk wie die Ablage: dann muss
Rippy die fertige Datei am Ende nur umhängen statt sie zu kopieren.</div></span>
<div class="mitknopf">
<input type="text" id="arbeit">
<button onclick="waehlen('arbeit')">Durchsuchen …</button>
</div>
</label>
<label class="feld" style="max-width:190px"><span>Port</span> <label class="feld" style="max-width:190px"><span>Port</span>
<input type="number" id="port" min="1024" max="65535"> <input type="number" id="port" min="1024" max="65535">
</label> </label>
@@ -532,6 +607,7 @@ SEITE = r"""<!DOCTYPE html>
await pywebview.api.installieren({ await pywebview.api.installieren({
ziel: $('ziel').value, ziel: $('ziel').value,
ablage: $('ablage').value, ablage: $('ablage').value,
arbeit: $('arbeit').value,
port: Number($('port').value), port: Number($('port').value),
autostart: $('autostart').checked, autostart: $('autostart').checked,
verknuepfungen: $('verknuepfungen').checked, verknuepfungen: $('verknuepfungen').checked,
@@ -578,6 +654,7 @@ SEITE = r"""<!DOCTYPE html>
const v = await pywebview.api.vorgaben(); const v = await pywebview.api.vorgaben();
$('ziel').value = v.ziel; $('ziel').value = v.ziel;
$('ablage').value = v.ablage; $('ablage').value = v.ablage;
$('arbeit').value = v.arbeit;
$('port').value = v.port; $('port').value = v.port;
$('autostart').checked = v.autostart; $('autostart').checked = v.autostart;
$('verknuepfungen').checked = v.verknuepfungen; $('verknuepfungen').checked = v.verknuepfungen;
+4 -3
View File
@@ -238,11 +238,12 @@ def test_vorgaben_sind_vollstaendig():
import pytest import pytest
pytest.skip("Registry-Vorgaben gibt es nur unter Windows") pytest.skip("Registry-Vorgaben gibt es nur unter Windows")
v = e.vorgaben() v = e.vorgaben()
assert set(v) == {"ziel", "ablage", "port", "autostart", assert set(v) == {"ziel", "ablage", "arbeit", "port", "autostart",
"verknuepfungen", "werkzeuge_holen"} "verknuepfungen", "werkzeuge_holen"}
assert v["port"] == 7788 assert v["port"] == 7788
assert v["ziel"] and v["ablage"] assert v["ziel"] and v["ablage"] and v["arbeit"]
assert "/app/" not in v["ablage"], "Container-Pfad in einer Windows-Vorgabe" for feld in ("ablage", "arbeit"):
assert "/app/" not in v[feld], "Container-Pfad in einer Windows-Vorgabe"
def test_netzwerkpfad_als_administrator_wird_erklaert(): def test_netzwerkpfad_als_administrator_wird_erklaert():
+93
View File
@@ -284,3 +284,96 @@ def test_updates_melden_was_offen_ist(monkeypatch):
assert [o["titel"] for o in stand["offen"]] == ["MakeMKV"] assert [o["titel"] for o in stand["offen"]] == ["MakeMKV"]
assert stand["offen"][0]["installiert"] == "1.18.2" assert stand["offen"][0]["installiert"] == "1.18.2"
assert stand["offen"][0]["neueste"] == "1.18.4" assert stand["offen"][0]["neueste"] == "1.18.4"
# ── Der Arbeitsordner im Setup (Commander 28.08.2026) ───────────────────
#
# > „kannst du noch einbauen das man den arbeitsordner usw. in den
# > Einstellungen setzen kann und direkt im setup?"
#
# Warum das nicht Kosmetik ist: Der Roh-Rip einer 4K-UHD ist bis zu 100 GB
# gross. Wer erst NACH der ersten vollen Platte erfaehrt, wo die liegen, hat
# es zu spaet erfahren — am 25.07.2026 lief genau so die VM-Platte voll.
def test_die_seite_fragt_nach_dem_arbeitsordner():
assert setup_fenster.seite_enthaelt('id="arbeit"')
assert setup_fenster.seite_enthaelt("Arbeitsordner für die Rohdaten")
def test_der_arbeitsordner_hat_einen_durchsuchen_knopf():
"""Einen Pfad abzutippen ist die Sorte Bedienung, die niemand macht."""
assert setup_fenster.seite_enthaelt("waehlen('arbeit')")
def test_jede_vorgabe_hat_ein_feld_UND_wird_mitgeschickt():
"""Der Waechter gegen halb angeschlossene Felder.
Ein Feld kann auf drei Arten kaputt sein, und alle drei sehen im Fenster
gleich aus: kein Eingabefeld, keine Vorbelegung, oder es wird beim
Installieren nicht mitgeschickt. Der dritte Fall ist der gemeinste — man
waehlt etwas, es passiert nichts, und niemand sagt warum.
"""
from rippy import einrichtung
# Schalter heissen im HTML anders (checkbox) und werden separat geprueft.
pfade_und_zahlen = {"ziel", "ablage", "arbeit", "port"}
for name in pfade_und_zahlen:
assert name in einrichtung.vorgaben(), "%s fehlt in vorgaben()" % name
assert setup_fenster.seite_enthaelt('id="%s"' % name), \
"kein Eingabefeld fuer %s" % name
assert setup_fenster.seite_enthaelt("$('%s').value = v.%s" % (name, name)), \
"%s wird nicht vorbelegt" % name
assert setup_fenster.seite_enthaelt("%s: " % name), \
"%s wird beim Installieren nicht mitgeschickt" % name
def test_der_arbeitsordner_landet_als_workDir_in_den_einstellungen(monkeypatch):
"""Der Name muss stimmen: Die Oberflaeche und der Worker lesen `workDir`."""
geschrieben = {}
monkeypatch.setattr(setup_fenster, "_einstellungen_schreiben",
lambda neu: geschrieben.update(neu))
monkeypatch.setattr(setup_fenster, "_beta_key_holen", lambda *a: "")
class StilleWerkzeuge:
@staticmethod
def sicherstellen(**k):
return {"bereit": True}
@staticmethod
def bericht(b):
return "ok"
import sys
import types
modul = types.ModuleType("rippy.tools.einrichten")
modul.sicherstellen = StilleWerkzeuge.sicherstellen
modul.bericht = StilleWerkzeuge.bericht
monkeypatch.setitem(sys.modules, "rippy.tools.einrichten", modul)
from rippy import windows_app
monkeypatch.setattr(windows_app, "installieren", lambda **k: r"C:\Rippy")
b = setup_fenster.Bruecke()
b._installieren_wirklich({"ablage": r"D:\Filme", "arbeit": r"E:\Roh",
"werkzeuge_holen": True})
assert geschrieben.get("workDir") == r"E:\Roh"
assert geschrieben.get("outputDir") == r"D:\Filme"
assert b.ergebnis["ok"] is True
def test_ohne_angabe_wird_NICHTS_geschrieben(monkeypatch):
"""Leer heisst „nimm die Vorgabe" — dann darf kein leerer Pfad in die
Einstellungen wandern und die Berechnung ueberschreiben."""
geschrieben = {}
monkeypatch.setattr(setup_fenster, "_einstellungen_schreiben",
lambda neu: geschrieben.update(neu))
monkeypatch.setattr(setup_fenster, "_beta_key_holen", lambda *a: "")
from rippy import windows_app
monkeypatch.setattr(windows_app, "installieren", lambda **k: r"C:\Rippy")
b = setup_fenster.Bruecke()
b._installieren_wirklich({"ablage": "", "arbeit": "", "werkzeuge_holen": False})
assert geschrieben == {}