Files
Multi-Debrid-Downloader/tests/mega-web-fallback.test.ts
T
Sucukdeluxe e1b4708952 fix: harden account rotation and active download recovery
Apply account enablement changes optimistically and persist rapid toggles sequentially without losing clicks.

Refresh Mega-Debrid API and Web account pools during active downloads, isolate per-account cancellation budgets, rotate within the same conversion attempt, and keep pause, stop, resume, reset, and no-account gates consistent.

Use the native Electron clipboard under Remote Desktop and make support bundle export bounded, redacted, responsive, guarded, visible, and atomic.

Add end-to-end regressions for live account switching, persisted partial recovery, support export, native copy, availability preservation, and the 500 ms telemetry cadence.
2026-08-13 05:14:44 +02:00

629 lines
30 KiB
TypeScript

import { afterEach, describe, expect, it, vi } from "vitest";
import { MegaWebFallback } from "../src/main/mega-web-fallback";
const originalFetch = globalThis.fetch;
describe("mega-web-fallback", () => {
afterEach(() => {
globalThis.fetch = originalFetch;
vi.restoreAllMocks();
});
describe("MegaWebFallback class", () => {
it("returns null when credentials are empty", async () => {
const fallback = new MegaWebFallback(() => ({ login: "", password: "" }));
const result = await fallback.unrestrict("https://mega.debrid/test");
expect(result).toBeNull();
});
it("does not restore an invalidated session when an older login finishes later", async () => {
let finishLogin: (cookie: string) => void = () => {};
const loginResult = new Promise<string>((resolve) => {
finishLogin = resolve;
});
const fallback = new MegaWebFallback(() => ({ login: "old-user", password: "old-pass" }));
const internals = fallback as unknown as {
ensureSession: (key: string, login: string, password: string) => Promise<string>;
login: (login: string, password: string) => Promise<string>;
sessions: Map<string, { cookie: string; setAt: number }>;
};
vi.spyOn(internals, "login").mockReturnValue(loginResult);
const pending = internals.ensureSession("old-user", "old-user", "old-pass");
await Promise.resolve();
fallback.invalidateSession();
finishLogin("stale-old-cookie");
await expect(pending).resolves.toBe("stale-old-cookie");
expect(internals.sessions.size).toBe(0);
});
it("does not cache an old session when an invalidated request retries login", async () => {
const fallback = new MegaWebFallback(() => ({ login: "old-user", password: "old-pass" }));
const internals = fallback as unknown as {
login: (login: string, password: string) => Promise<string>;
generate: (link: string, cookie: string) => Promise<{ directUrl: string; fileName: string } | null>;
sessions: Map<string, { cookie: string; setAt: number }>;
};
const login = vi.spyOn(internals, "login")
.mockResolvedValueOnce("first-stale-cookie")
.mockResolvedValueOnce("second-stale-cookie");
vi.spyOn(internals, "generate")
.mockImplementationOnce(async () => {
fallback.invalidateSession();
return null;
})
.mockResolvedValueOnce({ directUrl: "https://mega.direct/retry", fileName: "retry.bin" });
await expect(fallback.unrestrict("https://mega.debrid/retry", undefined, { login: "old-user", password: "old-pass" }))
.rejects.toThrow(/aborted/i);
expect(login).toHaveBeenCalledTimes(1);
expect(internals.sessions.size).toBe(0);
});
it("keeps a fresh session when an invalidated older request finishes later", async () => {
let releaseOldGenerate: () => void = () => {};
let markOldGenerateStarted: () => void = () => {};
const oldGenerateGate = new Promise<void>((resolve) => {
releaseOldGenerate = resolve;
});
const oldGenerateStarted = new Promise<void>((resolve) => {
markOldGenerateStarted = resolve;
});
const fallback = new MegaWebFallback(() => ({ login: "same-user", password: "same-pass" }));
const internals = fallback as unknown as {
login: (login: string, password: string, signal?: AbortSignal) => Promise<string>;
generate: (link: string, cookie: string, signal?: AbortSignal) => Promise<{ directUrl: string; fileName: string } | null>;
sessions: Map<string, { cookie: string; setAt: number }>;
};
const login = vi.spyOn(internals, "login")
.mockResolvedValueOnce("old-cookie")
.mockResolvedValueOnce("fresh-cookie");
vi.spyOn(internals, "generate").mockImplementation(async (link) => {
if (link.includes("old")) {
markOldGenerateStarted();
await oldGenerateGate;
return null;
}
return { directUrl: "https://mega.direct/fresh", fileName: "fresh.bin" };
});
const oldRequest = fallback.unrestrict("https://mega.debrid/old", undefined, { login: "same-user", password: "same-pass" });
const oldSettled = oldRequest.catch((error: unknown) => error);
await oldGenerateStarted;
fallback.invalidateSession();
const freshRequest = fallback.unrestrict("https://mega.debrid/fresh", undefined, { login: "same-user", password: "same-pass" });
try {
await expect(freshRequest).resolves.toMatchObject({ directUrl: "https://mega.direct/fresh" });
expect(internals.sessions.get("same-user")?.cookie).toBe("fresh-cookie");
} finally {
releaseOldGenerate();
}
await oldSettled;
expect(internals.sessions.get("same-user")?.cookie).toBe("fresh-cookie");
expect(login).toHaveBeenCalledTimes(2);
});
it("does not cache old credentials from a queued request after invalidation", async () => {
let releaseFirstLogin: () => void = () => {};
let markFirstLoginStarted: () => void = () => {};
const firstLoginGate = new Promise<void>((resolve) => {
releaseFirstLogin = resolve;
});
const firstLoginStarted = new Promise<void>((resolve) => {
markFirstLoginStarted = resolve;
});
const fallback = new MegaWebFallback(() => ({ login: "old-user", password: "old-pass" }));
const internals = fallback as unknown as {
login: (login: string, password: string) => Promise<string>;
generate: (link: string, cookie: string) => Promise<{ directUrl: string; fileName: string }>;
sessions: Map<string, { cookie: string; setAt: number }>;
};
let loginCount = 0;
vi.spyOn(internals, "login").mockImplementation(async () => {
loginCount += 1;
if (loginCount === 1) {
markFirstLoginStarted();
await firstLoginGate;
}
return `old-cookie-${loginCount}`;
});
vi.spyOn(internals, "generate").mockResolvedValue({ directUrl: "https://mega.direct/queued", fileName: "queued.bin" });
const first = fallback.unrestrict("https://mega.debrid/first", undefined, { login: "old-user", password: "old-pass" });
const firstSettled = first.catch((error: unknown) => error);
await firstLoginStarted;
const queued = fallback.unrestrict("https://mega.debrid/queued", undefined, { login: "old-user", password: "old-pass" });
const queuedSettled = queued.catch((error: unknown) => error);
fallback.invalidateSession();
releaseFirstLogin();
await expect(firstSettled).resolves.toBeInstanceOf(Error);
await expect(queuedSettled).resolves.toBeInstanceOf(Error);
expect(loginCount).toBe(1);
expect(internals.sessions.size).toBe(0);
});
it("does not let a blocked old account job block a new request after session invalidation", async () => {
let releaseOldLogin: () => void = () => {};
let markOldLoginStarted: () => void = () => {};
const oldLoginGate = new Promise<void>((resolve) => {
releaseOldLogin = resolve;
});
const oldLoginStarted = new Promise<void>((resolve) => {
markOldLoginStarted = resolve;
});
const fallback = new MegaWebFallback(() => ({ login: "same-user", password: "same-pass" }));
const internals = fallback as unknown as {
login: (login: string, password: string, signal?: AbortSignal) => Promise<string>;
generate: (link: string, cookie: string, signal?: AbortSignal) => Promise<{ directUrl: string; fileName: string }>;
};
const login = vi.spyOn(internals, "login")
.mockImplementationOnce(async () => {
markOldLoginStarted();
await oldLoginGate;
return "old-cookie";
})
.mockResolvedValueOnce("new-cookie");
vi.spyOn(internals, "generate").mockImplementation(async (link) => ({
directUrl: link.includes("fresh") ? "https://mega.direct/fresh" : "https://mega.direct/old",
fileName: link.includes("fresh") ? "fresh.bin" : "old.bin"
}));
const oldRequest = fallback.unrestrict("https://mega.debrid/old", undefined, { login: "same-user", password: "same-pass" });
const oldSettled = oldRequest.catch((error: unknown) => error);
await oldLoginStarted;
fallback.invalidateSession();
const freshRequest = fallback.unrestrict("https://mega.debrid/fresh", undefined, { login: "same-user", password: "same-pass" });
try {
await vi.waitFor(() => expect(login).toHaveBeenCalledTimes(2), { timeout: 500, interval: 5 });
await expect(freshRequest).resolves.toMatchObject({
directUrl: "https://mega.direct/fresh",
fileName: "fresh.bin"
});
} finally {
releaseOldLogin();
await Promise.allSettled([oldSettled, freshRequest]);
}
});
it("does not execute provider work for a queued request aborted before it starts", async () => {
let releaseFirstLogin: () => void = () => {};
let markFirstLoginStarted: () => void = () => {};
const firstLoginGate = new Promise<void>((resolve) => {
releaseFirstLogin = resolve;
});
const firstLoginStarted = new Promise<void>((resolve) => {
markFirstLoginStarted = resolve;
});
const generatedLinks: string[] = [];
const fallback = new MegaWebFallback(() => ({ login: "same-user", password: "same-pass" }));
const internals = fallback as unknown as {
login: (login: string, password: string, signal?: AbortSignal) => Promise<string>;
generate: (link: string, cookie: string, signal?: AbortSignal) => Promise<{ directUrl: string; fileName: string }>;
queues: Map<string, Promise<unknown>>;
};
const login = vi.spyOn(internals, "login").mockImplementation(async () => {
markFirstLoginStarted();
await firstLoginGate;
return "first-cookie";
});
vi.spyOn(internals, "generate").mockImplementation(async (link) => {
generatedLinks.push(link);
return { directUrl: "https://mega.direct/first", fileName: "first.bin" };
});
const firstRequest = fallback.unrestrict("https://mega.debrid/first", undefined, { login: "same-user", password: "same-pass" });
await firstLoginStarted;
const queuedController = new AbortController();
const queuedRequest = fallback.unrestrict("https://mega.debrid/aborted", queuedController.signal, { login: "same-user", password: "same-pass" });
const queueTail = internals.queues.get("same-user");
queuedController.abort("cancel-before-start");
await expect(queuedRequest).rejects.toThrow(/queue.?timeout/i);
releaseFirstLogin();
await expect(firstRequest).resolves.toMatchObject({ directUrl: "https://mega.direct/first" });
await queueTail;
expect(login).toHaveBeenCalledTimes(1);
expect(generatedLinks).toEqual(["https://mega.debrid/first"]);
});
it("starts a new request normally after invalidating a queue with an aborted waiter", async () => {
let releaseOldLogin: () => void = () => {};
let markOldLoginStarted: () => void = () => {};
const oldLoginGate = new Promise<void>((resolve) => {
releaseOldLogin = resolve;
});
const oldLoginStarted = new Promise<void>((resolve) => {
markOldLoginStarted = resolve;
});
const generatedLinks: string[] = [];
const fallback = new MegaWebFallback(() => ({ login: "same-user", password: "same-pass" }));
const internals = fallback as unknown as {
login: (login: string, password: string, signal?: AbortSignal) => Promise<string>;
generate: (link: string, cookie: string, signal?: AbortSignal) => Promise<{ directUrl: string; fileName: string }>;
queues: Map<string, Promise<unknown>>;
};
const login = vi.spyOn(internals, "login")
.mockImplementationOnce(async () => {
markOldLoginStarted();
await oldLoginGate;
return "old-cookie";
})
.mockResolvedValueOnce("fresh-cookie");
vi.spyOn(internals, "generate").mockImplementation(async (link) => {
generatedLinks.push(link);
return {
directUrl: link.includes("fresh") ? "https://mega.direct/fresh" : "https://mega.direct/old",
fileName: link.includes("fresh") ? "fresh.bin" : "old.bin"
};
});
const oldRequest = fallback.unrestrict("https://mega.debrid/old", undefined, { login: "same-user", password: "same-pass" });
const oldSettled = oldRequest.catch((error: unknown) => error);
await oldLoginStarted;
const queuedController = new AbortController();
const queuedRequest = fallback.unrestrict("https://mega.debrid/aborted", queuedController.signal, { login: "same-user", password: "same-pass" });
const staleQueueTail = internals.queues.get("same-user");
queuedController.abort("cancel-before-start");
await expect(queuedRequest).rejects.toThrow(/queue.?timeout/i);
fallback.invalidateSession();
const freshRequest = fallback.unrestrict("https://mega.debrid/fresh", undefined, { login: "same-user", password: "same-pass" });
try {
await vi.waitFor(() => expect(login).toHaveBeenCalledTimes(2), { timeout: 500, interval: 5 });
await expect(freshRequest).resolves.toMatchObject({
directUrl: "https://mega.direct/fresh",
fileName: "fresh.bin"
});
} finally {
releaseOldLogin();
await Promise.allSettled([oldSettled, freshRequest, staleQueueTail]);
}
expect(generatedLinks).toEqual(["https://mega.debrid/fresh"]);
});
it("logs in, fetches HTML, parses code, and polls AJAX for direct url", async () => {
let fetchCallCount = 0;
globalThis.fetch = vi.fn(async (url: string | URL | Request) => {
const urlStr = String(url);
fetchCallCount += 1;
if (urlStr.includes("form=login")) {
const headers = new Headers();
headers.append("set-cookie", "session=goodcookie; path=/");
return new Response("", { headers, status: 200 });
}
if (urlStr.includes("page=debrideur")) {
return new Response('<form id="debridForm"></form>', { status: 200 });
}
if (urlStr.includes("form=debrid")) {
return new Response(`
<div class="acp-box">
<h3>Link: https://mega.debrid/link1</h3>
<a href="javascript:processDebrid(1,'secretcode123',0)">Download</a>
</div>
`, { status: 200 });
}
if (urlStr.includes("ajax=debrid")) {
return new Response(JSON.stringify({ link: "https://mega.direct/123" }), { status: 200 });
}
return new Response("Not found", { status: 404 });
}) as unknown as typeof fetch;
const fallback = new MegaWebFallback(() => ({ login: "user", password: "pwd" }));
const result = await fallback.unrestrict("https://mega.debrid/link1");
expect(result).not.toBeNull();
expect(result?.directUrl).toBe("https://mega.direct/123");
expect(result?.fileName).toBe("link1");
expect(fetchCallCount).toBe(4);
});
it("fails fast on 'Kein Server für diesen Hoster' (account hoster quota) instead of re-login + re-poll", async () => {
let ajaxCalls = 0;
globalThis.fetch = vi.fn(async (url: string | URL | Request) => {
const urlStr = String(url);
if (urlStr.includes("form=login")) {
const headers = new Headers();
headers.append("set-cookie", "session=goodcookie; path=/");
return new Response("", { headers, status: 200 });
}
if (urlStr.includes("page=debrideur")) {
return new Response('<form id="debridForm"></form>', { status: 200 });
}
if (urlStr.includes("form=debrid")) {
return new Response(`<div class="acp-box"><h3>Link: https://mega.debrid/l1</h3><a href="javascript:processDebrid(1,'code1',0)">d</a></div>`, { status: 200 });
}
if (urlStr.includes("ajax=debrid")) {
ajaxCalls += 1;
return new Response(JSON.stringify({ link: "", text: "Erreur : Kein Server für diesen Hoster verfügbar. Bitte versuchen Sie es später noch einmal." }), { status: 200 });
}
return new Response("Not found", { status: 404 });
}) as unknown as typeof fetch;
const fallback = new MegaWebFallback(() => ({ login: "user", password: "pwd" }));
await expect(fallback.unrestrict("https://mega.debrid/l1")).rejects.toThrow(/kein server für diesen hoster/i);
expect(ajaxCalls).toBe(1);
});
it("surfaces 'Kein Server für diesen Hoster' from the debrid PAGE (daily limit, no debrid code) instead of empty", async () => {
let ajaxCalls = 0;
globalThis.fetch = vi.fn(async (url: string | URL | Request) => {
const urlStr = String(url);
if (urlStr.includes("form=login")) {
const headers = new Headers();
headers.append("set-cookie", "session=goodcookie; path=/");
return new Response("", { headers, status: 200 });
}
if (urlStr.includes("page=debrideur")) {
return new Response('<form id="debridForm"></form>', { status: 200 });
}
if (urlStr.includes("form=debrid")) {
return new Response('<div class="error">Erreur : Kein Server für diesen Hoster verfügbar. Bitte versuchen Sie es später noch einmal.</div>', { status: 200 });
}
if (urlStr.includes("ajax=debrid")) {
ajaxCalls += 1;
return new Response(JSON.stringify({ link: "https://should.not/happen" }), { status: 200 });
}
return new Response("Not found", { status: 404 });
}) as unknown as typeof fetch;
const fallback = new MegaWebFallback(() => ({ login: "user", password: "pwd" }));
await expect(fallback.unrestrict("https://mega.debrid/l1")).rejects.toThrow(/kein server für diesen hoster/i);
expect(ajaxCalls).toBe(0);
});
it("logs in with the per-account credentials passed to unrestrict, not the default", async () => {
const loginsUsed: string[] = [];
globalThis.fetch = vi.fn(async (url: string | URL | Request, opts?: { body?: unknown }) => {
const urlStr = String(url);
if (urlStr.includes("form=login")) {
const params = new URLSearchParams(String(opts?.body ?? ""));
loginsUsed.push(params.get("login") || "");
const headers = new Headers();
headers.append("set-cookie", "session=goodcookie; path=/");
return new Response("", { headers, status: 200 });
}
if (urlStr.includes("page=debrideur")) {
return new Response('<form id="debridForm"></form>', { status: 200 });
}
if (urlStr.includes("form=debrid")) {
return new Response(`<div class="acp-box"><h3>Link: https://mega.debrid/l1</h3><a href="javascript:processDebrid(1,'code1',0)">d</a></div>`, { status: 200 });
}
if (urlStr.includes("ajax=debrid")) {
return new Response(JSON.stringify({ link: "https://mega.direct/ok" }), { status: 200 });
}
return new Response("Not found", { status: 404 });
}) as unknown as typeof fetch;
const fallback = new MegaWebFallback(() => ({ login: "defaultacc", password: "defpw" }));
const result = await fallback.unrestrict("https://mega.debrid/l1", undefined, { login: "account2", password: "pw2" });
expect(result?.directUrl).toBe("https://mega.direct/ok");
expect(loginsUsed).toContain("account2");
expect(loginsUsed).not.toContain("defaultacc");
});
it("throws if login fails to set cookie", async () => {
globalThis.fetch = vi.fn(async (url: string | URL | Request) => {
const urlStr = String(url);
if (urlStr.includes("form=login")) {
const headers = new Headers();
return new Response("", { headers, status: 200 });
}
return new Response("Not found", { status: 404 });
}) as unknown as typeof fetch;
const fallback = new MegaWebFallback(() => ({ login: "bad", password: "bad" }));
await expect(fallback.unrestrict("http://mega.debrid/file"))
.rejects.toThrow("Mega-Web Login liefert kein Session-Cookie");
});
it("throws if login verify check fails (no form found)", async () => {
globalThis.fetch = vi.fn(async (url: string | URL | Request) => {
const urlStr = String(url);
if (urlStr.includes("form=login")) {
const headers = new Headers();
headers.append("set-cookie", "session=goodcookie; path=/");
return new Response("", { headers, status: 200 });
}
if (urlStr.includes("page=debrideur")) {
return new Response('<html><body>Nothing here</body></html>', { status: 200 });
}
return new Response("Not found", { status: 404 });
}) as unknown as typeof fetch;
const fallback = new MegaWebFallback(() => ({ login: "a", password: "b" }));
await expect(fallback.unrestrict("http://mega.debrid/file"))
.rejects.toThrow("Mega-Web Login ungültig oder Session blockiert");
});
it("returns null if generation fails to find a code", async () => {
let callCount = 0;
globalThis.fetch = vi.fn(async (url: string | URL | Request) => {
const urlStr = String(url);
callCount++;
if (urlStr.includes("form=login")) {
const headers = new Headers();
headers.append("set-cookie", "session=goodcookie; path=/");
return new Response("", { headers, status: 200 });
}
if (urlStr.includes("page=debrideur")) {
return new Response('<form id="debridForm"></form>', { status: 200 });
}
if (urlStr.includes("form=debrid")) {
return new Response(`<div>No links here</div>`, { status: 200 });
}
return new Response("Not found", { status: 404 });
}) as unknown as typeof fetch;
const fallback = new MegaWebFallback(() => ({ login: "a", password: "b" }));
const result = await fallback.unrestrict("http://mega.debrid/file");
expect(result).toBeNull();
});
it("serialisiert gleichzeitige Umwandlungen auf DEMSELBEN Account (kein Doppel-Login)", async () => {
let loginCount = 0;
globalThis.fetch = vi.fn(async (url: string | URL | Request) => {
const u = String(url);
if (u.includes("form=login")) {
loginCount += 1;
await new Promise((r) => setTimeout(r, 15));
const headers = new Headers();
headers.append("set-cookie", "session=c; path=/");
return new Response("", { headers, status: 200 });
}
if (u.includes("page=debrideur")) return new Response('<form id="debridForm"></form>', { status: 200 });
if (u.includes("form=debrid")) return new Response(`<div class="acp-box"><h3>Link: https://mega.debrid/l</h3><a href="javascript:processDebrid(1,'code',0)">d</a></div>`, { status: 200 });
if (u.includes("ajax=debrid")) return new Response(JSON.stringify({ link: "https://mega.direct/ok" }), { status: 200 });
return new Response("Not found", { status: 404 });
}) as unknown as typeof fetch;
const fallback = new MegaWebFallback(() => ({ login: "same", password: "pw" }));
const [r1, r2] = await Promise.all([
fallback.unrestrict("https://mega.debrid/a", undefined, { login: "same", password: "pw" }),
fallback.unrestrict("https://mega.debrid/b", undefined, { login: "same", password: "pw" })
]);
expect(r1?.directUrl).toBe("https://mega.direct/ok");
expect(r2?.directUrl).toBe("https://mega.direct/ok");
// Serialisiert auf demselben Account → der zweite nutzt die gecachte Session, kein zweiter Login.
expect(loginCount).toBe(1);
});
it("wandelt auf VERSCHIEDENEN Accounts parallel um (Logins laufen gleichzeitig)", async () => {
let activeLogins = 0;
let maxActiveLogins = 0;
const bothInFlight = new Promise<void>((resolve) => {
const check = setInterval(() => { if (activeLogins >= 2) { clearInterval(check); resolve(); } }, 5);
});
globalThis.fetch = vi.fn(async (url: string | URL | Request) => {
const u = String(url);
if (u.includes("form=login")) {
activeLogins += 1;
maxActiveLogins = Math.max(maxActiveLogins, activeLogins);
await bothInFlight;
activeLogins -= 1;
const headers = new Headers();
headers.append("set-cookie", "session=c; path=/");
return new Response("", { headers, status: 200 });
}
if (u.includes("page=debrideur")) return new Response('<form id="debridForm"></form>', { status: 200 });
if (u.includes("form=debrid")) return new Response(`<div class="acp-box"><h3>Link: https://mega.debrid/l</h3><a href="javascript:processDebrid(1,'code',0)">d</a></div>`, { status: 200 });
if (u.includes("ajax=debrid")) return new Response(JSON.stringify({ link: "https://mega.direct/ok" }), { status: 200 });
return new Response("Not found", { status: 404 });
}) as unknown as typeof fetch;
const fallback = new MegaWebFallback(() => ({ login: "d", password: "p" }));
const [r1, r2] = await Promise.all([
fallback.unrestrict("https://mega.debrid/a", undefined, { login: "acc1", password: "p" }),
fallback.unrestrict("https://mega.debrid/b", undefined, { login: "acc2", password: "p" })
]);
expect(r1?.directUrl).toBe("https://mega.direct/ok");
expect(r2?.directUrl).toBe("https://mega.direct/ok");
// Verschiedene Accounts → beide Logins gleichzeitig in-flight (sonst haengt es am bothInFlight-Barrier).
expect(maxActiveLogins).toBe(2);
}, 10000);
it("aborts pending Mega-Web polling when signal is cancelled", async () => {
globalThis.fetch = vi.fn((url: string | URL | Request, init?: RequestInit): Promise<Response> => {
const urlStr = String(url);
if (urlStr.includes("form=login")) {
const headers = new Headers();
headers.append("set-cookie", "session=goodcookie; path=/");
return Promise.resolve(new Response("", { headers, status: 200 }));
}
if (urlStr.includes("page=debrideur")) {
return Promise.resolve(new Response('<form id="debridForm"></form>', { status: 200 }));
}
if (urlStr.includes("form=debrid")) {
return Promise.resolve(new Response(`
<div class="acp-box">
<h3>Link: https://mega.debrid/link2</h3>
<a href="javascript:processDebrid(1,'secretcode456',0)">Download</a>
</div>
`, { status: 200 }));
}
if (urlStr.includes("ajax=debrid")) {
return new Promise<Response>((_resolve, reject) => {
const signal = init?.signal;
const onAbort = (): void => reject(new Error("aborted:ajax"));
if (signal?.aborted) {
onAbort();
return;
}
signal?.addEventListener("abort", onAbort, { once: true });
});
}
return Promise.resolve(new Response("Not found", { status: 404 }));
}) as unknown as typeof fetch;
const fallback = new MegaWebFallback(() => ({ login: "user", password: "pwd" }));
const controller = new AbortController();
const timer = setTimeout(() => {
controller.abort("test");
}, 200);
try {
await expect(fallback.unrestrict("https://mega.debrid/link2", controller.signal)).rejects.toThrow(/aborted/i);
} finally {
clearTimeout(timer);
}
});
it("klassifiziert einen Abbruch WAEHREND in der Queue als Queue-Timeout (nicht harter Abbruch), damit der belegte Account nicht bestraft wird", async () => {
let releaseLogin: () => void = () => {};
const loginGate = new Promise<void>((resolve) => { releaseLogin = resolve; });
globalThis.fetch = vi.fn(async (url: string | URL | Request) => {
const u = String(url);
if (u.includes("form=login")) {
await loginGate;
const headers = new Headers();
headers.append("set-cookie", "session=c; path=/");
return new Response("", { headers, status: 200 });
}
if (u.includes("page=debrideur")) return new Response('<form id="debridForm"></form>', { status: 200 });
if (u.includes("form=debrid")) return new Response(`<div class="acp-box"><h3>Link: https://mega.debrid/l</h3><a href="javascript:processDebrid(1,'code',0)">d</a></div>`, { status: 200 });
if (u.includes("ajax=debrid")) return new Response(JSON.stringify({ link: "https://mega.direct/ok" }), { status: 200 });
return new Response("Not found", { status: 404 });
}) as unknown as typeof fetch;
const fallback = new MegaWebFallback(() => ({ login: "same", password: "pw" }));
const firstCtrl = new AbortController();
const first = fallback.unrestrict("https://mega.debrid/a", firstCtrl.signal, { login: "same", password: "pw" });
await new Promise((r) => setTimeout(r, 25));
const secondCtrl = new AbortController();
const second = fallback.unrestrict("https://mega.debrid/b", secondCtrl.signal, { login: "same", password: "pw" });
await new Promise((r) => setTimeout(r, 25));
secondCtrl.abort("caller-timeout");
await expect(second).rejects.toThrow(/queue.?timeout/i);
releaseLogin();
await first.catch(() => null);
await new Promise((r) => setTimeout(r, 20));
}, 10000);
});
});