real-debrid-downloader/tests/unrestrict-retry.test.ts
Sucukdeluxe 3bd6e3b23e Härtung Download/Rotation (Audit-Batch 1): Scheduler-Freeze, Cooldown-Respekt, Daily-Reset, Kategorisierung
Aus einem adversarisch verifizierten Multi-Agent-Audit (14 confirmed/11 refuted):

#1 HIGH Scheduler-Freeze: findNextQueuedItem hatte keinen activeTasks-Guard.
Wird ein Item zurueckgesetzt/ueberschrieben, waehrend sein alter Task noch in
einem nicht-abbrechbaren await parkt (z.B. Integritaets-Check), liefert
findNextQueuedItem dasselbe Item, startItem lehnt es ab ohne activeTasks zu
verkleinern → der SYNCHRONE Admission-Loop dreht endlos → Event-Loop friert
permanent ein. Fix: `if (this.activeTasks.has(itemId)) continue;`. Repro-Test
(ohne Fix haengt sogar der vitest-Timeout — Freeze bewiesen).

#2/#3 MED mega_debrid_cooldown:<ms> wurde verworfen: kein Parser (nur das
debrid_link-Analogon) → Item lief in den generischen 5s-Exponential-Backoff
und fragte cooled Accounts im Sekundentakt erneut an. Neuer
parseMegaDebridCooldownRetry (nimmt das frueheste Cooldown-Ende ueber alle
Accounts) + Handler VOR transient/generic → Item wartet die echte Cooldown-Zeit.

#7/#8 MED Mega per-Account Tages-Usage wurde am Tageswechsel nie zurueckgesetzt
(ensureProviderDailyUsageFresh ruecksetzte nur provider/debrid-link), und weil
es den Tagesschluessel zuerst weiterstellte, lief auch der Reset in
addMegaDebridAccountDailyUsageBytes ins Leere → Accounts blieben den ganzen Tag
faelschlich "am Limit" und schrumpften das (neue) Pro-Account-Umwandlungslimit.
Fix: megaDebridAccountDailyUsageBytes im Tageswechsel mit zuruecksetzen.

#14 LOW classifyAccountFailure: rate_limit-Branch vor quota (quota matchte
"limit" in "rate limit" → Fehl-Kategorisierung).

852/852 gruen, tsc unveraendert (6). #4 (empty→until-restart) bewusst deferred.
2026-06-17 04:22:20 +02:00

61 lines
2.7 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { transientResolveRetryDelayMs, parseMegaDebridCooldownRetry } 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;
}
});
});
describe("parseMegaDebridCooldownRetry (honor the encoded account-cooldown delay)", () => {
it("parses the encoded delay from a bare mega_debrid_cooldown error", () => {
const r = parseMegaDebridCooldownRetry("mega_debrid_cooldown:20330:Mega-Debrid (Account 2/2, Da******el): Token error");
expect(r).not.toBeNull();
expect(r!.delayMs).toBe(20330);
expect(r!.detail).toContain("Mega-Debrid");
});
it("parses it when embedded in the aggregated provider-chain error", () => {
const aggregated = "Unrestrict fehlgeschlagen: Mega-Debrid API: mega_debrid_cooldown:20330:Mega-Debrid (Account 2/2): Token error";
expect(parseMegaDebridCooldownRetry(aggregated)!.delayMs).toBe(20330);
});
it("takes the SOONEST (min) cooldown when several accounts are cooled", () => {
const both = "Mega-Debrid API: mega_debrid_cooldown:116285:web | Mega-Debrid API: mega_debrid_cooldown:20330:api";
expect(parseMegaDebridCooldownRetry(both)!.delayMs).toBe(20330);
});
it("clamps to [1s, 15min]", () => {
expect(parseMegaDebridCooldownRetry("mega_debrid_cooldown:1:x")!.delayMs).toBe(1000);
expect(parseMegaDebridCooldownRetry("mega_debrid_cooldown:99999999:x")!.delayMs).toBe(15 * 60 * 1000);
});
it("returns null when there is no mega cooldown marker", () => {
expect(parseMegaDebridCooldownRetry("Datei beim Hoster gerade nicht abrufbar")).toBeNull();
expect(parseMegaDebridCooldownRetry("debrid_link_cooldown:5000:x")).toBeNull();
});
});