Restore package-based link collector without blocking DLC drops
Reintroduce expandable collector packages, background metadata enrichment, filters, selection, and controlled queue transfer. Keep DLC files dropped outside the collector on the direct addContainers path without settings persistence or metadata waits. Protect collector state with stable URL identities, non-degrading metadata merges, and per-URL generations so stale enrichment responses cannot overwrite newer data or resurrect removed links.
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { defaultSettings } from "../src/main/constants";
|
||||
import {
|
||||
enrichCollectorPackages,
|
||||
prepareCollectorContainers,
|
||||
prepareCollectorText
|
||||
} from "../src/main/collector-inspection";
|
||||
import {
|
||||
validateCollectorContainerPreparationRequest,
|
||||
validateCollectorEnrichmentRequest,
|
||||
validateCollectorTextPreparationRequest
|
||||
} from "../src/shared/collector";
|
||||
|
||||
describe("collector preparation", () => {
|
||||
it("returns a stable package skeleton without requesting metadata", () => {
|
||||
const fetchRequest = vi.spyOn(globalThis, "fetch");
|
||||
const rawText = [
|
||||
"# Package: Staffel A",
|
||||
"# File: episode.part01.rar",
|
||||
"https://example.com/a",
|
||||
"https://example.com/a",
|
||||
"invalid"
|
||||
].join("\n");
|
||||
|
||||
const first = prepareCollectorText({ rawText, addedAt: 1_000 });
|
||||
const second = prepareCollectorText({ rawText, addedAt: 2_000 });
|
||||
|
||||
expect(fetchRequest).not.toHaveBeenCalled();
|
||||
expect(first.invalidCount).toBe(1);
|
||||
expect(first.duplicateCount).toBe(1);
|
||||
expect(first.packages).toHaveLength(1);
|
||||
expect(first.packages[0]).toEqual(expect.objectContaining({
|
||||
name: "Staffel A",
|
||||
nameSource: "explicit"
|
||||
}));
|
||||
expect(first.packages[0].links[0]).toEqual(expect.objectContaining({
|
||||
url: "https://example.com/a",
|
||||
fileName: "episode.part01.rar",
|
||||
availability: "unknown",
|
||||
status: "ready"
|
||||
}));
|
||||
expect(second.packages[0].id).toBe(first.packages[0].id);
|
||||
expect(second.packages[0].links[0].id).toBe(first.packages[0].links[0].id);
|
||||
fetchRequest.mockRestore();
|
||||
});
|
||||
|
||||
it("decrypts selected DLC files into a skeleton without metadata enrichment", async () => {
|
||||
const importContainers = vi.fn(async () => [{
|
||||
name: "DLC Paket",
|
||||
links: ["https://1fichier.com/?abc123def456ghi789jk"],
|
||||
fileNames: ["episode.part01.rar"]
|
||||
}]);
|
||||
|
||||
const result = await prepareCollectorContainers(
|
||||
["C:\\Imports\\sample.dlc"],
|
||||
3_000,
|
||||
{ importContainers }
|
||||
);
|
||||
|
||||
expect(importContainers).toHaveBeenCalledWith(["C:\\Imports\\sample.dlc"]);
|
||||
expect(result.packages[0]).toEqual(expect.objectContaining({
|
||||
name: "DLC Paket",
|
||||
nameSource: "explicit"
|
||||
}));
|
||||
expect(result.packages[0].links[0]).toEqual(expect.objectContaining({
|
||||
fileName: "episode.part01.rar",
|
||||
availability: "unknown",
|
||||
status: "ready"
|
||||
}));
|
||||
});
|
||||
|
||||
it("rejects oversized text and invalid container paths", () => {
|
||||
expect(() => validateCollectorTextPreparationRequest({ rawText: "x".repeat(2_000_001), addedAt: 1 })).toThrow(/ungültig/i);
|
||||
expect(() => validateCollectorTextPreparationRequest({ rawText: "ä".repeat(1_100_000), addedAt: 1 })).toThrow(/ungültig/i);
|
||||
expect(() => validateCollectorContainerPreparationRequest({
|
||||
filePaths: Array.from({ length: 101 }, (_, index) => `C:\\Imports\\${index}.dlc`),
|
||||
addedAt: 1
|
||||
})).toThrow(/ungültig/i);
|
||||
expect(() => validateCollectorContainerPreparationRequest({ filePaths: ["relative.dlc"], addedAt: 1 })).toThrow(/ungültig/i);
|
||||
});
|
||||
});
|
||||
|
||||
describe("collector enrichment", () => {
|
||||
it("updates known links by URL while preserving their stable ids", async () => {
|
||||
const prepared = prepareCollectorText({
|
||||
rawText: "https://1fichier.com/?abc123def456ghi789jk",
|
||||
addedAt: 4_000
|
||||
});
|
||||
const linkBefore = prepared.packages[0].links[0];
|
||||
|
||||
const result = await enrichCollectorPackages(
|
||||
{ packages: prepared.packages },
|
||||
defaultSettings(),
|
||||
{
|
||||
checkOneFichier: async () => new Map([[linkBefore.url, {
|
||||
online: true,
|
||||
fileName: "Show.S01E01.part01.rar",
|
||||
fileSizeBytes: 471_859_200,
|
||||
accessRestricted: false
|
||||
}]])
|
||||
}
|
||||
);
|
||||
|
||||
expect(result.packages).toHaveLength(1);
|
||||
expect(result.packages[0].name).toBe("Show.S01E01");
|
||||
expect(result.packages[0].links[0]).toEqual(expect.objectContaining({
|
||||
id: linkBefore.id,
|
||||
url: linkBefore.url,
|
||||
fileName: "Show.S01E01.part01.rar",
|
||||
fileSizeBytes: 471_859_200,
|
||||
availability: "online",
|
||||
status: "ready"
|
||||
}));
|
||||
});
|
||||
|
||||
it("runs independent enrichments concurrently instead of serializing them globally", async () => {
|
||||
const first = prepareCollectorText({ rawText: "https://1fichier.com/?first", addedAt: 5_000 });
|
||||
const second = prepareCollectorText({ rawText: "https://1fichier.com/?second", addedAt: 6_000 });
|
||||
const started: string[] = [];
|
||||
const resolvers: Array<() => void> = [];
|
||||
const checkOneFichier = async (links: string[]) => {
|
||||
started.push(links[0]);
|
||||
await new Promise<void>((resolve) => resolvers.push(resolve));
|
||||
return new Map();
|
||||
};
|
||||
|
||||
const firstRun = enrichCollectorPackages({ packages: first.packages }, defaultSettings(), { checkOneFichier });
|
||||
const secondRun = enrichCollectorPackages({ packages: second.packages }, defaultSettings(), { checkOneFichier });
|
||||
await vi.waitFor(() => expect(started).toHaveLength(2));
|
||||
resolvers.forEach((resolve) => resolve());
|
||||
await Promise.all([firstRun, secondRun]);
|
||||
|
||||
expect(started).toEqual([
|
||||
"https://1fichier.com/?first",
|
||||
"https://1fichier.com/?second"
|
||||
]);
|
||||
});
|
||||
|
||||
it("rejects enrichment payloads that do not contain prepared absolute links", () => {
|
||||
expect(() => validateCollectorEnrichmentRequest({ packages: [] })).toThrow(/ungültig/i);
|
||||
expect(() => validateCollectorEnrichmentRequest({
|
||||
packages: [{
|
||||
id: "package",
|
||||
name: "Paket",
|
||||
nameSource: "inferred",
|
||||
addedAt: 1,
|
||||
links: [{
|
||||
id: "link",
|
||||
url: "relative",
|
||||
fileName: "",
|
||||
fileSizeBytes: null,
|
||||
hoster: "",
|
||||
availability: "unknown",
|
||||
status: "unknown",
|
||||
addedAt: 1
|
||||
}]
|
||||
}]
|
||||
})).toThrow(/ungültig/i);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user