R3 (Kern): Transiente Resolve-Fehler ("Datei beim Hoster gerade nicht
abrufbar", frz. "Fichier supprimé") werden jetzt mit kurzem Delay
(3–6–10s) neu versucht statt mit dem 5s..120s-Exponential. Ein solcher
Einzel-Fehler zählt nicht mehr zum Provider-Circuit-Breaker (der bei 20
Fehlern 30–300s Cooldown setzt und die Mega-Session invalidiert) und
löst keine Provider-Cooldown-Inflation aus — dadurch bremst er die
gesunden Links nicht mehr aus. Neuer Branch vor der generischen
Unrestrict-Retry-Logik; Detektor isMegaDebridTransientResolveFailure
matcht sowohl die rohe französische als auch die gerenderte deutsche
Meldung (am Download-Manager kommt die klassifizierte deutsche an).
R2 (parallel): getSerializedValidatingLimit für megadebrid-web ist nicht
mehr hart 1, sondern die Anzahl nutzbarer Accounts. Der harte Wert 1
stammte aus der Einzel-Account-Zeit (Commit a3e3d6f). Sicher, weil die
Web-Ebene ohnehin pro Account serialisiert (runExclusive, key=login) und
die Account-Rotation gleichzeitige Aufrufe atomar auf distinkte Accounts
verteilt (megaDebridInFlight wird synchron vor dem await erhöht). Ein
Account → Limit 1 (keine Änderung).
R4 (Sichtbarkeit): Der echte Grund steht inline im Status und als Tooltip
(item.lastError); kaputten "?"-Separator zu "(Rn)" gesäubert.
UI-Bugfixes: Account-Duplizierung beim Entfernen (React-Key-Kollision
API/Web → rowKey nach kind), Username-Spalte zeigt eingegebenen Login
statt Anbieter-Mail (Mail als Tooltip).
Tests: transientResolveRetryDelayMs, isMegaDebridTransientResolveFailure,
2 behaviorale Download-Manager-Tests (schneller Re-Try mit deutschem
Grund; 1 Web-Umwandlung pro Account parallel). 833/833 grün, tsc
unverändert.
32 lines
1.2 KiB
TypeScript
32 lines
1.2 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { transientResolveRetryDelayMs } from "../src/main/download-manager";
|
|
|
|
describe("transientResolveRetryDelayMs (fast, bounded retry for transient resolve failures)", () => {
|
|
it("starts fast (<= 3s) instead of the 5s..120s exponential", () => {
|
|
expect(transientResolveRetryDelayMs(1)).toBeLessThanOrEqual(3000);
|
|
});
|
|
|
|
it("ramps gently and caps at 10s", () => {
|
|
expect(transientResolveRetryDelayMs(2)).toBeLessThanOrEqual(7000);
|
|
expect(transientResolveRetryDelayMs(3)).toBeLessThanOrEqual(10000);
|
|
expect(transientResolveRetryDelayMs(10)).toBe(10000);
|
|
expect(transientResolveRetryDelayMs(100)).toBe(10000);
|
|
});
|
|
|
|
it("never schedules anywhere near the 5s..120s exponential cap", () => {
|
|
for (let n = 1; n <= 50; n += 1) {
|
|
expect(transientResolveRetryDelayMs(n)).toBeLessThanOrEqual(10000);
|
|
expect(transientResolveRetryDelayMs(n)).toBeGreaterThanOrEqual(1000);
|
|
}
|
|
});
|
|
|
|
it("is monotonic non-decreasing", () => {
|
|
let prev = 0;
|
|
for (let n = 1; n <= 12; n += 1) {
|
|
const d = transientResolveRetryDelayMs(n);
|
|
expect(d).toBeGreaterThanOrEqual(prev);
|
|
prev = d;
|
|
}
|
|
});
|
|
});
|