Allow read-only folder tests independently of monitoring state
CI / verify (push) Waiting to run

This commit is contained in:
Sucukdeluxe
2026-09-22 06:07:27 +02:00
parent 9d696fa89f
commit b4c3c075b7
5 changed files with 95 additions and 4 deletions
+2
View File
@@ -6,6 +6,7 @@ Multi-Hoster-Upload ist eine Electron-Desktopanwendung für Windows, die große
## Aktueller Zustand ## Aktueller Zustand
- Ordnerüberwachungstest nach `2.1.49`: IPC verwendet gespeicherte Ordnerregeln in einer isolierten FolderMonitor-Instanz statt der möglicherweise unkonfigurierten laufenden Überwachung. Funktioniert bei deaktivierter, aktiver und pausierter Überwachung; kein Watcher-/Timerstart, keine neuen Dateien/Statusereignisse am produktiven Monitor, keine Änderung seiner Generation oder Duplikatreservierungen. Fehlender Pfad, unerreichbarer Ordner und Scanfehler werden über feste, übersetzte Fehlermeldungen angezeigt; unbekannte Fehler bleiben bereinigt. Regressionen für die drei Betriebszustände, parallele Tests, Fehlerfälle und IPC-Verdrahtung ergänzt; Entwicklung automatisch neu gestartet.
- Automatik-Einstellungen nach `2.1.49`: gemeinsames 210-px-Beschriftungsraster für Wiederholungen, Ordnerpfad, Queue-Limit, Abgleichintervall, Dateifilter und Verzögerung. Felder 40 px hoch, Zahlenfelder 120 px breit, Dropdowns auf passende Breiten begrenzt und Pfeile 14 px nach innen versetzt. Hinweise stehen unter ihrem jeweiligen Feld; Container-Abfragen schalten bei schmalem Inhalt auf einspaltige Darstellung. Ordnerüberwachungstest steht nach Verhalten/Hoster-Vorauswahl als Abschlussaktion. IDs und Speicherlogik unverändert. Electron-Regression mit echtem Template bei 1000/760/360 px, Screenshot visuell geprüft; noch kein Release. - Automatik-Einstellungen nach `2.1.49`: gemeinsames 210-px-Beschriftungsraster für Wiederholungen, Ordnerpfad, Queue-Limit, Abgleichintervall, Dateifilter und Verzögerung. Felder 40 px hoch, Zahlenfelder 120 px breit, Dropdowns auf passende Breiten begrenzt und Pfeile 14 px nach innen versetzt. Hinweise stehen unter ihrem jeweiligen Feld; Container-Abfragen schalten bei schmalem Inhalt auf einspaltige Darstellung. Ordnerüberwachungstest steht nach Verhalten/Hoster-Vorauswahl als Abschlussaktion. IDs und Speicherlogik unverändert. Electron-Regression mit echtem Template bei 1000/760/360 px, Screenshot visuell geprüft; noch kein Release.
- Online-Backup-Seite nach `2.1.49`: drei klar abgegrenzte Bereiche in Reihenfolge „Schlüssel erstellen“, „Schlüssel importieren“, „Auf diesem Gerät erstellt“. Vorhandene IDs, Import-/Exportlogik, Statusmeldungen und Suchziele bleiben erhalten. Neue Überschriften auf Deutsch/Englisch; konsistente Karten ohne Akzentstreifen. Electron-Test nutzt jetzt das echte Seitenmarkup und prüft Modulzuordnung, Ausrichtung sowie Überlauf bei 1000/600/360 px. Lokale Entwicklerversion automatisch neu gestartet; breiter Screenshot visuell geprüft. Noch nicht veröffentlicht. - Online-Backup-Seite nach `2.1.49`: drei klar abgegrenzte Bereiche in Reihenfolge „Schlüssel erstellen“, „Schlüssel importieren“, „Auf diesem Gerät erstellt“. Vorhandene IDs, Import-/Exportlogik, Statusmeldungen und Suchziele bleiben erhalten. Neue Überschriften auf Deutsch/Englisch; konsistente Karten ohne Akzentstreifen. Electron-Test nutzt jetzt das echte Seitenmarkup und prüft Modulzuordnung, Ausrichtung sowie Überlauf bei 1000/600/360 px. Lokale Entwicklerversion automatisch neu gestartet; breiter Screenshot visuell geprüft. Noch nicht veröffentlicht.
- Lokale Entwicklung auf Nutzerwunsch dauerhaft mit automatischem Neustart: `.artifacts/dev-watch.cjs` überwacht Main-/Preload-Dateien, `lib`, `renderer` und den lokalen Bootstrap `.artifacts/menu-test.cjs`. Start über `node .artifacts/dev-watch.cjs`; separater Fenstertitel „Multi-Hoster Entwicklung (automatischer Neustart)“. Persistentes Testprofil unter `%LOCALAPPDATA%/Multi-Hoster-Upload-Development`, ohne automatische Accountchecks/Ordnerüberwachung/Remote-Steuerung initialisiert. Installierte Anwendung bleibt getrennt. Neustart durch CSS-Dateiänderung anhand gewechselter Electron-PID und erneut sichtbarem Fenster verifiziert. Bei weiteren Code-/UI-Änderungen diesen Watcher verwenden und den Neustart prüfen. - Lokale Entwicklung auf Nutzerwunsch dauerhaft mit automatischem Neustart: `.artifacts/dev-watch.cjs` überwacht Main-/Preload-Dateien, `lib`, `renderer` und den lokalen Bootstrap `.artifacts/menu-test.cjs`. Start über `node .artifacts/dev-watch.cjs`; separater Fenstertitel „Multi-Hoster Entwicklung (automatischer Neustart)“. Persistentes Testprofil unter `%LOCALAPPDATA%/Multi-Hoster-Upload-Development`, ohne automatische Accountchecks/Ordnerüberwachung/Remote-Steuerung initialisiert. Installierte Anwendung bleibt getrennt. Neustart durch CSS-Dateiänderung anhand gewechselter Electron-PID und erneut sichtbarem Fenster verifiziert. Bei weiteren Code-/UI-Änderungen diesen Watcher verwenden und den Neustart prüfen.
@@ -93,6 +94,7 @@ npm audit --omit=dev
## Zuletzt verifiziert ## Zuletzt verifiziert
- Test bei deaktivierter Ordnerüberwachung: 844 Haupttests, 18 Servertests und Lint erfolgreich. Watcher-Neustart bestätigt. Read-only-Test bleibt unabhängig vom Produktivzustand, bekannte Fehler werden konkret angezeigt. Noch nicht veröffentlicht.
- Automatik-Layout: 840 Haupttests, 18 Servertests und Lint erfolgreich. Feldhöhen, gemeinsame linke Kante, Label-/Hinweispositionen, Dropdownbreiten/Pfeilabstand und Abschlussposition der Testaktion über echte Electron-DOM-Messungen geprüft. Watcher-Neustart und sichtbare Entwicklungsinstanz bestätigt. Noch nicht veröffentlicht. - Automatik-Layout: 840 Haupttests, 18 Servertests und Lint erfolgreich. Feldhöhen, gemeinsame linke Kante, Label-/Hinweispositionen, Dropdownbreiten/Pfeilabstand und Abschlussposition der Testaktion über echte Electron-DOM-Messungen geprüft. Watcher-Neustart und sichtbare Entwicklungsinstanz bestätigt. Noch nicht veröffentlicht.
- Getrennte Online-Backup-Module: 839 Haupttests, 18 Servertests und Lint erfolgreich. Echtes Modulmarkup in Electron bei drei Fensterbreiten geprüft; Entwicklerversion nach Renderer-Änderung mit neuem Prozess sichtbar bestätigt. Noch kein Release. - Getrennte Online-Backup-Module: 839 Haupttests, 18 Servertests und Lint erfolgreich. Echtes Modulmarkup in Electron bei drei Fensterbreiten geprüft; Entwicklerversion nach Renderer-Änderung mit neuem Prozess sichtbar bestätigt. Noch kein Release.
- Gültigkeitsdauer-Layout: neue Electron-Regression erfolgreich, Screenshot visuell geprüft, Lint und 18 Servertests erfolgreich. Hauptlauf 838/839 erfolgreich: Menü-Fokustest reagierte einmal zu früh auf asynchronen Fokus. Test wartet jetzt begrenzt auf den tatsächlichen Fokus statt pauschal 240 ms; kompletter Wiederholungslauf der betroffenen Datei 25/25 erfolgreich. Kein Release erstellt. - Gültigkeitsdauer-Layout: neue Electron-Regression erfolgreich, Screenshot visuell geprüft, Lint und 18 Servertests erfolgreich. Hauptlauf 838/839 erfolgreich: Menü-Fokustest reagierte einmal zu früh auf asynchronen Fokus. Test wartet jetzt begrenzt auf den tatsächlichen Fokus statt pauschal 240 ms; kompletter Wiederholungslauf der betroffenen Datei 25/25 erfolgreich. Kein Release erstellt.
+16
View File
@@ -81,6 +81,22 @@ class FolderMonitor extends EventEmitter {
this._deactivate({ clearSeen: true, paused: false }); this._deactivate({ clearSeen: true, paused: false });
} }
async testScan(settings) {
if (!String(settings?.folderPath || '').trim()) {
return this._result([], false, false, 'test', { error: 'Kein Ordnerpfad angegeben' });
}
const isolated = new FolderMonitor({
access: this._access,
stat: this._stat,
walkFolder: this._walkFolder,
now: this._now
});
isolated.configure({ ...settings });
const result = await isolated.scan({ emitFiles: false, trigger: 'test' });
if (!result.reachable) return { ...result, error: 'Ordner nicht erreichbar' };
return result;
}
status() { status() {
return Object.freeze({ return Object.freeze({
running: this.running, running: this.running,
+1 -1
View File
@@ -3712,7 +3712,7 @@ ipcMain.handle('automation:resume', () => enqueueAutomationLifecycle(async gener
})); }));
ipcMain.handle('folder-monitor:test-scan', () => { ipcMain.handle('folder-monitor:test-scan', () => {
return folderMonitor.scan({ emitFiles: false, trigger: 'test' }); return folderMonitor.testScan(configStore.load().globalSettings?.folderMonitor || {});
}); });
ipcMain.handle('folder-monitor:reconcile', () => { ipcMain.handle('folder-monitor:reconcile', () => {
+7 -3
View File
@@ -927,7 +927,10 @@ async function applyAutomationEvaluation(evaluation) {
async function runFolderMonitorTestScan() { async function runFolderMonitorTestScan() {
const result = await window.api.folderMonitorTestScan(); const result = await window.api.folderMonitorTestScan();
if (result?.error) throw new Error('Ordnerüberwachung konnte nicht getestet werden.'); if (result?.error) {
const knownErrors = ['Kein Ordnerpfad angegeben', 'Ordner nicht erreichbar', 'Ordnerscan fehlgeschlagen'];
throw new Error(knownErrors.includes(result.error) ? result.error : 'Ordnerüberwachung konnte nicht getestet werden.');
}
return evaluateAutomationCandidates(result?.files || [], { dryRun: true, trigger: result?.trigger || 'test' }); return evaluateAutomationCandidates(result?.files || [], { dryRun: true, trigger: result?.trigger || 'test' });
} }
@@ -1226,9 +1229,10 @@ async function runAutomationTestOverlay() {
const evaluation = await runFolderMonitorTestScan(); const evaluation = await runFolderMonitorTestScan();
if (generation !== automationTestGeneration) return; if (generation !== automationTestGeneration) return;
renderAutomationTestViewState({ loading: false, summary: evaluation.summary, error: '' }); renderAutomationTestViewState({ loading: false, summary: evaluation.summary, error: '' });
} catch { } catch (error) {
if (generation !== automationTestGeneration) return; if (generation !== automationTestGeneration) return;
renderAutomationTestViewState({ loading: false, summary: null, error: 'Ordnerüberwachung konnte nicht getestet werden.' }); const knownErrors = ['Kein Ordnerpfad angegeben', 'Ordner nicht erreichbar', 'Ordnerscan fehlgeschlagen'];
renderAutomationTestViewState({ loading: false, summary: null, error: knownErrors.includes(error?.message) ? error.message : 'Ordnerüberwachung konnte nicht getestet werden.' });
} }
} }
+69
View File
@@ -19,6 +19,75 @@ function createWatcherHarness() {
return { calls, monitor: new FolderMonitor({ watch, ...timers }) }; return { calls, monitor: new FolderMonitor({ watch, ...timers }) };
} }
test('test scan works while inactive, active or paused without changing monitor state', async () => {
const folderPath = path.join(os.tmpdir(), 'monitor-read-only-test');
for (const state of ['inactive', 'active', 'paused']) {
const { monitor, events } = createScanHarness({ files: [
{ path: path.join(folderPath, 'video.mkv'), size: 12, mtimeMs: 1 },
{ path: path.join(folderPath, 'note.txt'), size: 2, mtimeMs: 2 }
] });
if (state === 'active') monitor.start({ folderPath: 'different-live-folder' });
if (state === 'paused') monitor.configure({ folderPath: 'different-paused-folder', paused: true });
const before = monitor.status();
const generation = monitor._generation;
const settings = monitor._settings;
const statuses = events.statuses.length;
const seen = [...monitor._seenFiles];
const result = await monitor.testScan({ enabled: false, folderPath, extensions: 'mkv', filterMode: 'include', recursive: true });
assert.equal(result.reachable, true);
assert.deepEqual(result.files.map(file => file.filterMatched), [true, false]);
assert.deepEqual(monitor.status(), before);
assert.equal(monitor._generation, generation);
assert.equal(monitor._settings, settings);
assert.deepEqual([...monitor._seenFiles], seen);
assert.equal(events.statuses.length, statuses);
assert.equal(events.newFiles.length, 0);
monitor.stop();
}
});
test('test scan uses independent settings for concurrent read-only requests', async () => {
const visited = [];
const monitor = new FolderMonitor({
access: async () => {},
walkFolder: async (folder, options) => { visited.push([folder, options.recursive]); return []; },
watch: () => { throw new Error('No watcher may be started'); },
setIntervalFn: () => { throw new Error('No timer may be started'); }
});
await Promise.all([
monitor.testScan({ folderPath: 'first', recursive: false }),
monitor.testScan({ folderPath: 'second', recursive: true })
]);
assert.deepEqual(visited, [['first', false], ['second', true]]);
assert.equal(monitor.running, false);
assert.equal(monitor.status().folderPath, '');
});
test('test scan distinguishes missing, unavailable and unreadable folders safely', async () => {
const missing = await new FolderMonitor().testScan({});
assert.equal(missing.error, 'Kein Ordnerpfad angegeben');
const unavailable = await new FolderMonitor({ access: async () => { throw new Error('private path'); } }).testScan({ folderPath: 'test' });
assert.equal(unavailable.error, 'Ordner nicht erreichbar');
const failed = await new FolderMonitor({ access: async () => {}, walkFolder: async () => { throw new Error('private path'); } }).testScan({ folderPath: 'test' });
assert.equal(failed.error, 'Ordnerscan fehlgeschlagen');
});
test('test scan IPC reads persisted settings instead of depending on the active watcher', async () => {
const vm = require('node:vm');
const source = fs.readFileSync(path.join(__dirname, '../main.js'), 'utf8');
const start = source.indexOf("ipcMain.handle('folder-monitor:test-scan'");
const end = source.indexOf('\n});', start) + 4;
const settings = { enabled: false, folderPath: 'test-folder' };
let handler;
const expected = { files: [], reachable: true };
vm.runInNewContext(source.slice(start, end), {
ipcMain: { handle: (_name, callback) => { handler = callback; } },
configStore: { load: () => ({ globalSettings: { folderMonitor: settings } }) },
folderMonitor: { testScan: snapshot => { assert.equal(snapshot, settings); return expected; } }
});
assert.equal(await handler(), expected);
});
function createManualTimers() { function createManualTimers() {
const intervals = new Set(); const intervals = new Set();
const timeouts = new Set(); const timeouts = new Set();