Share one pure provider selection plan between real unrestrict routing and cooldown projection, including direct 1Fichier and DDownload paths, disabled Mega aliases, and the secondary-provider exception used when Real-Debrid is cooling down. Attach terminal observers to raw serialized web jobs before evaluating already-aborted signals so late rejections stay handled while the queue remains available. Add RED-to-GREEN regressions for each provider-selection counterexample and pre-aborted Real-Debrid, AllDebrid, and BestDebrid jobs.
240 lines
8.0 KiB
TypeScript
240 lines
8.0 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
const {
|
|
mockFromPartition,
|
|
mockSession,
|
|
mockFetch,
|
|
mockBrowserWindowCtor,
|
|
mockLoadURL,
|
|
mockShow,
|
|
mockFocus,
|
|
mockClose,
|
|
mockSetWindowOpenHandler,
|
|
mockSetPermissionRequestHandler
|
|
} = vi.hoisted(() => {
|
|
const fetch = vi.fn();
|
|
const clearStorageData = vi.fn();
|
|
const clearCache = vi.fn();
|
|
const fromPartition = vi.fn();
|
|
const loadURL = vi.fn(async () => {});
|
|
const show = vi.fn();
|
|
const focus = vi.fn();
|
|
const setWindowOpenHandler = vi.fn();
|
|
const setPermissionRequestHandler = vi.fn();
|
|
const windowEvents: Record<string, (...args: unknown[]) => void> = {};
|
|
let destroyed = false;
|
|
|
|
const browserWindow = {
|
|
isDestroyed: vi.fn(() => destroyed),
|
|
isMinimized: vi.fn(() => false),
|
|
restore: vi.fn(),
|
|
show,
|
|
focus,
|
|
close: vi.fn(() => {
|
|
destroyed = true;
|
|
windowEvents.closed?.();
|
|
}),
|
|
setMenuBarVisibility: vi.fn(),
|
|
loadURL,
|
|
on: vi.fn((event: string, handler: (...args: unknown[]) => void) => {
|
|
windowEvents[event] = handler;
|
|
return browserWindow;
|
|
}),
|
|
webContents: {
|
|
setWindowOpenHandler,
|
|
on: vi.fn(),
|
|
session: {
|
|
setPermissionRequestHandler
|
|
}
|
|
}
|
|
};
|
|
|
|
const BrowserWindowCtor = vi.fn((_options: unknown) => {
|
|
destroyed = false;
|
|
return browserWindow;
|
|
});
|
|
|
|
return {
|
|
mockFromPartition: fromPartition,
|
|
mockSession: {
|
|
fetch,
|
|
clearStorageData,
|
|
clearCache
|
|
},
|
|
mockFetch: fetch,
|
|
mockBrowserWindowCtor: BrowserWindowCtor,
|
|
mockLoadURL: loadURL,
|
|
mockShow: show,
|
|
mockFocus: focus,
|
|
mockClose: browserWindow.close,
|
|
mockSetWindowOpenHandler: setWindowOpenHandler,
|
|
mockSetPermissionRequestHandler: setPermissionRequestHandler
|
|
};
|
|
});
|
|
|
|
vi.mock("electron", () => ({
|
|
session: {
|
|
fromPartition: mockFromPartition
|
|
},
|
|
BrowserWindow: mockBrowserWindowCtor,
|
|
shell: {
|
|
openExternal: vi.fn()
|
|
}
|
|
}));
|
|
|
|
import { AllDebridWebFallback } from "../src/main/all-debrid-web";
|
|
|
|
describe("alldebrid-web", () => {
|
|
beforeEach(() => {
|
|
mockFromPartition.mockReturnValue(mockSession);
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.clearAllMocks();
|
|
mockFromPartition.mockReturnValue(mockSession);
|
|
});
|
|
|
|
it("opens the AllDebrid login window with the shared restrictive browser boundary", async () => {
|
|
const fallback = new AllDebridWebFallback(() => true);
|
|
|
|
await fallback.openLoginWindow();
|
|
|
|
expect(mockBrowserWindowCtor).toHaveBeenCalledTimes(1);
|
|
expect(mockBrowserWindowCtor.mock.calls[0]?.[0]).toEqual(expect.objectContaining({
|
|
webPreferences: {
|
|
partition: "persist:alldebrid-web",
|
|
contextIsolation: true,
|
|
nodeIntegration: false,
|
|
sandbox: true,
|
|
webSecurity: true,
|
|
allowRunningInsecureContent: false
|
|
}
|
|
}));
|
|
expect(mockSetWindowOpenHandler).toHaveBeenCalledTimes(1);
|
|
expect(mockSetPermissionRequestHandler).toHaveBeenCalledTimes(1);
|
|
expect(mockLoadURL).toHaveBeenCalledWith("https://alldebrid.com/register/?from=de");
|
|
expect(mockShow).toHaveBeenCalled();
|
|
expect(mockFocus).toHaveBeenCalled();
|
|
});
|
|
|
|
it("uses an existing AllDebrid Web session to unrestrict without opening a login window", async () => {
|
|
mockFetch.mockResolvedValueOnce(new Response(JSON.stringify({
|
|
link: "https://alldebrid.direct/session-file.bin",
|
|
filename: "session-file.bin",
|
|
filesize: 9876
|
|
}), { status: 200 }));
|
|
const fallback = new AllDebridWebFallback(() => true);
|
|
|
|
const result = await fallback.unrestrict("https://rapidgator.net/file/session");
|
|
|
|
expect(result).toEqual({
|
|
directUrl: "https://alldebrid.direct/session-file.bin",
|
|
fileName: "session-file.bin",
|
|
fileSize: 9876,
|
|
retriesUsed: 0
|
|
});
|
|
expect(mockBrowserWindowCtor).not.toHaveBeenCalled();
|
|
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
expect(mockFetch.mock.calls[0]?.[0]).toBe("https://alldebrid.com/service.php");
|
|
expect(mockFetch.mock.calls[0]?.[1]).toEqual(expect.objectContaining({
|
|
method: "POST",
|
|
body: "link=https%3A%2F%2Frapidgator.net%2Ffile%2Fsession&nb=0&json=true&pw="
|
|
}));
|
|
});
|
|
|
|
it("releases an aborted caller while the active web request ignores its signal", async () => {
|
|
let rejectRequest!: (error: Error) => void;
|
|
mockFetch
|
|
.mockReturnValueOnce(new Promise<Response>((_resolve, reject) => {
|
|
rejectRequest = reject;
|
|
}))
|
|
.mockResolvedValueOnce(new Response(JSON.stringify({
|
|
link: "https://alldebrid.direct/second.bin",
|
|
filename: "second.bin",
|
|
filesize: 333
|
|
}), { status: 200 }));
|
|
const fallback = new AllDebridWebFallback(() => true);
|
|
const controller = new AbortController();
|
|
const running = fallback.unrestrict("https://rapidgator.net/file/abort-race", controller.signal)
|
|
.then(() => "resolved" as const, (error) => String(error));
|
|
|
|
await vi.waitFor(() => expect(mockFetch).toHaveBeenCalledTimes(1));
|
|
controller.abort("test-stop");
|
|
const outcome = await Promise.race([
|
|
running,
|
|
new Promise<"timeout">((resolve) => setTimeout(() => resolve("timeout"), 200))
|
|
]);
|
|
|
|
expect(outcome).toContain("aborted:alldebrid-web");
|
|
const secondOutcome = await Promise.race([
|
|
fallback.unrestrict("https://rapidgator.net/file/second"),
|
|
new Promise<"timeout">((resolve) => setTimeout(() => resolve("timeout"), 200))
|
|
]);
|
|
expect(secondOutcome).toEqual({
|
|
directUrl: "https://alldebrid.direct/second.bin",
|
|
fileName: "second.bin",
|
|
fileSize: 333,
|
|
retriesUsed: 0
|
|
});
|
|
expect(mockFetch).toHaveBeenCalledTimes(2);
|
|
rejectRequest(new Error("late alldebrid rejection"));
|
|
await Promise.resolve();
|
|
});
|
|
|
|
it("observes the raw rejection when the signal is already aborted and keeps the queue usable", async () => {
|
|
mockFetch.mockResolvedValue(new Response(JSON.stringify({
|
|
link: "https://alldebrid.direct/next.bin",
|
|
filename: "next.bin",
|
|
filesize: 666
|
|
}), { status: 200 }));
|
|
const fallback = new AllDebridWebFallback(() => true);
|
|
const controller = new AbortController();
|
|
controller.abort("before-queue");
|
|
const unhandled: unknown[] = [];
|
|
const onUnhandled = (reason: unknown) => unhandled.push(reason);
|
|
process.on("unhandledRejection", onUnhandled);
|
|
try {
|
|
await expect(fallback.unrestrict("https://rapidgator.net/file/pre-aborted", controller.signal))
|
|
.rejects.toThrow("aborted:alldebrid-web");
|
|
await new Promise((resolve) => setImmediate(resolve));
|
|
await expect(fallback.unrestrict("https://rapidgator.net/file/next")).resolves.toMatchObject({
|
|
directUrl: "https://alldebrid.direct/next.bin",
|
|
fileName: "next.bin"
|
|
});
|
|
expect(unhandled).toEqual([]);
|
|
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
} finally {
|
|
process.off("unhandledRejection", onUnhandled);
|
|
}
|
|
});
|
|
|
|
it("opens the login window after login_required and retries generation with the same session partition", async () => {
|
|
mockFetch
|
|
.mockResolvedValueOnce(new Response("login", { status: 200 }))
|
|
.mockResolvedValueOnce(new Response(JSON.stringify({
|
|
link: "https://alldebrid.direct/retry-file.bin",
|
|
filename: "retry-file.bin",
|
|
filesize: 12345
|
|
}), { status: 200 }));
|
|
const fallback = new AllDebridWebFallback(() => true);
|
|
|
|
const result = await fallback.unrestrict("https://rapidgator.net/file/retry");
|
|
|
|
expect(result).toEqual({
|
|
directUrl: "https://alldebrid.direct/retry-file.bin",
|
|
fileName: "retry-file.bin",
|
|
fileSize: 12345,
|
|
retriesUsed: 0
|
|
});
|
|
expect(mockBrowserWindowCtor).toHaveBeenCalledTimes(1);
|
|
expect(mockLoadURL).toHaveBeenCalledWith("https://alldebrid.com/register/?from=de");
|
|
expect(mockShow).toHaveBeenCalled();
|
|
expect(mockFocus).toHaveBeenCalled();
|
|
expect(mockSetWindowOpenHandler).toHaveBeenCalledTimes(1);
|
|
expect(mockSetPermissionRequestHandler).toHaveBeenCalledTimes(1);
|
|
expect(mockClose).toHaveBeenCalledTimes(1);
|
|
expect(mockFromPartition).toHaveBeenCalledWith("persist:alldebrid-web");
|
|
expect(mockFetch).toHaveBeenCalledTimes(2);
|
|
});
|
|
});
|