Refactor: Extractor in 18 Sektionen reorganisiert

This commit is contained in:
Sucukdeluxe
2026-03-10 23:47:02 +01:00
parent b49de16534
commit d9170f4167
25 changed files with 114170 additions and 30695 deletions
+86 -86
View File
@@ -1,86 +1,86 @@
import { describe, expect, it } from "vitest";
import { encryptBackup, decryptBackup } from "../src/main/backup-crypto";
describe("backup-crypto", () => {
it("encrypts and decrypts a round-trip correctly", () => {
const original = JSON.stringify({
version: 2,
settings: { token: "my-secret-api-key", outputDir: "C:\\Downloads" },
session: { packages: {}, items: {} },
history: [{ id: "h1", name: "Test" }]
});
const encrypted = encryptBackup(original);
const decrypted = decryptBackup(encrypted);
expect(decrypted).toBe(original);
});
it("produces binary output that is not plaintext readable", () => {
const secret = "super-secret-token-12345";
const plaintext = JSON.stringify({ settings: { token: secret } });
const encrypted = encryptBackup(plaintext);
// The encrypted buffer should NOT contain the secret in plaintext
expect(encrypted.toString("utf8")).not.toContain(secret);
expect(encrypted.toString("latin1")).not.toContain(secret);
});
it("starts with the MDD1 magic bytes", () => {
const encrypted = encryptBackup("test");
expect(encrypted.subarray(0, 4).toString("utf8")).toBe("MDD1");
});
it("produces different ciphertext for the same input (random IV)", () => {
const plaintext = "same input data";
const a = encryptBackup(plaintext);
const b = encryptBackup(plaintext);
// IVs are different, so full buffers must differ
expect(a.equals(b)).toBe(false);
// But both decrypt to the same plaintext
expect(decryptBackup(a)).toBe(plaintext);
expect(decryptBackup(b)).toBe(plaintext);
});
it("throws on truncated data", () => {
const encrypted = encryptBackup("test data");
const truncated = encrypted.subarray(0, 10);
expect(() => decryptBackup(truncated)).toThrow();
});
it("throws on corrupted ciphertext", () => {
const encrypted = encryptBackup("test data");
// Flip a byte in the ciphertext area
const corrupted = Buffer.from(encrypted);
corrupted[corrupted.length - 1] ^= 0xff;
expect(() => decryptBackup(corrupted)).toThrow();
});
it("throws on wrong magic bytes", () => {
const encrypted = encryptBackup("test data");
const wrongMagic = Buffer.from(encrypted);
wrongMagic[0] = 0x00;
expect(() => decryptBackup(wrongMagic)).toThrow(/Signatur/);
});
it("throws on empty buffer", () => {
expect(() => decryptBackup(Buffer.alloc(0))).toThrow();
});
it("handles large payloads", () => {
const large = JSON.stringify({ data: "x".repeat(1_000_000) });
const encrypted = encryptBackup(large);
const decrypted = decryptBackup(encrypted);
expect(decrypted).toBe(large);
});
it("handles unicode content", () => {
const unicode = JSON.stringify({ name: "Ünïcödé 日本語 🎉", path: "C:\\Benutzer\\Ö" });
const encrypted = encryptBackup(unicode);
expect(decryptBackup(encrypted)).toBe(unicode);
});
it("handles empty string round-trip", () => {
const encrypted = encryptBackup("");
expect(decryptBackup(encrypted)).toBe("");
});
});
import { describe, expect, it } from "vitest";
import { encryptBackup, decryptBackup } from "../src/main/backup-crypto";
describe("backup-crypto", () => {
it("encrypts and decrypts a round-trip correctly", () => {
const original = JSON.stringify({
version: 2,
settings: { token: "my-secret-api-key", outputDir: "C:\\Downloads" },
session: { packages: {}, items: {} },
history: [{ id: "h1", name: "Test" }]
});
const encrypted = encryptBackup(original);
const decrypted = decryptBackup(encrypted);
expect(decrypted).toBe(original);
});
it("produces binary output that is not plaintext readable", () => {
const secret = "super-secret-token-12345";
const plaintext = JSON.stringify({ settings: { token: secret } });
const encrypted = encryptBackup(plaintext);
// The encrypted buffer should NOT contain the secret in plaintext
expect(encrypted.toString("utf8")).not.toContain(secret);
expect(encrypted.toString("latin1")).not.toContain(secret);
});
it("starts with the MDD1 magic bytes", () => {
const encrypted = encryptBackup("test");
expect(encrypted.subarray(0, 4).toString("utf8")).toBe("MDD1");
});
it("produces different ciphertext for the same input (random IV)", () => {
const plaintext = "same input data";
const a = encryptBackup(plaintext);
const b = encryptBackup(plaintext);
// IVs are different, so full buffers must differ
expect(a.equals(b)).toBe(false);
// But both decrypt to the same plaintext
expect(decryptBackup(a)).toBe(plaintext);
expect(decryptBackup(b)).toBe(plaintext);
});
it("throws on truncated data", () => {
const encrypted = encryptBackup("test data");
const truncated = encrypted.subarray(0, 10);
expect(() => decryptBackup(truncated)).toThrow();
});
it("throws on corrupted ciphertext", () => {
const encrypted = encryptBackup("test data");
// Flip a byte in the ciphertext area
const corrupted = Buffer.from(encrypted);
corrupted[corrupted.length - 1] ^= 0xff;
expect(() => decryptBackup(corrupted)).toThrow();
});
it("throws on wrong magic bytes", () => {
const encrypted = encryptBackup("test data");
const wrongMagic = Buffer.from(encrypted);
wrongMagic[0] = 0x00;
expect(() => decryptBackup(wrongMagic)).toThrow(/Signatur/);
});
it("throws on empty buffer", () => {
expect(() => decryptBackup(Buffer.alloc(0))).toThrow();
});
it("handles large payloads", () => {
const large = JSON.stringify({ data: "x".repeat(1_000_000) });
const encrypted = encryptBackup(large);
const decrypted = decryptBackup(encrypted);
expect(decrypted).toBe(large);
});
it("handles unicode content", () => {
const unicode = JSON.stringify({ name: "Ünïcödé 日本語 🎉", path: "C:\\Benutzer\\Ö" });
const encrypted = encryptBackup(unicode);
expect(decryptBackup(encrypted)).toBe(unicode);
});
it("handles empty string round-trip", () => {
const encrypted = encryptBackup("");
expect(decryptBackup(encrypted)).toBe("");
});
});
+109 -109
View File
@@ -1,109 +1,109 @@
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import { cleanupCancelledPackageArtifacts, removeDownloadLinkArtifacts, removeSampleArtifacts } from "../src/main/cleanup";
const tempDirs: string[] = [];
afterEach(() => {
for (const dir of tempDirs.splice(0)) {
fs.rmSync(dir, { recursive: true, force: true });
}
});
describe("cleanup", () => {
it("removes archive artifacts but keeps media", () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "rd-clean-"));
tempDirs.push(dir);
fs.writeFileSync(path.join(dir, "release.part1.rar"), "x");
fs.writeFileSync(path.join(dir, "movie.mkv"), "x");
const removed = cleanupCancelledPackageArtifacts(dir);
expect(removed).toBeGreaterThan(0);
expect(fs.existsSync(path.join(dir, "release.part1.rar"))).toBe(false);
expect(fs.existsSync(path.join(dir, "movie.mkv"))).toBe(true);
});
it("removes sample artifacts and link files", async () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "rd-clean-"));
tempDirs.push(dir);
fs.mkdirSync(path.join(dir, "Samples"), { recursive: true });
fs.writeFileSync(path.join(dir, "Samples", "demo-sample.mkv"), "x");
fs.writeFileSync(path.join(dir, "download_links.txt"), "https://example.com/a\n");
const links = await removeDownloadLinkArtifacts(dir);
const samples = await removeSampleArtifacts(dir);
expect(links).toBeGreaterThan(0);
expect(samples.files + samples.dirs).toBeGreaterThan(0);
});
it("cleans up archive files in nested directories", () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "rd-clean-"));
tempDirs.push(dir);
// Create nested directory structure with archive files
const sub1 = path.join(dir, "season1");
const sub2 = path.join(dir, "season1", "extras");
fs.mkdirSync(sub2, { recursive: true });
fs.writeFileSync(path.join(sub1, "episode.part1.rar"), "x");
fs.writeFileSync(path.join(sub1, "episode.part2.rar"), "x");
fs.writeFileSync(path.join(sub2, "bonus.zip"), "x");
fs.writeFileSync(path.join(sub2, "bonus.7z"), "x");
// Non-archive files should be kept
fs.writeFileSync(path.join(sub1, "video.mkv"), "real content");
fs.writeFileSync(path.join(sub2, "subtitle.srt"), "subtitle content");
const removed = cleanupCancelledPackageArtifacts(dir);
expect(removed).toBe(4); // 2 rar parts + zip + 7z
expect(fs.existsSync(path.join(sub1, "episode.part1.rar"))).toBe(false);
expect(fs.existsSync(path.join(sub1, "episode.part2.rar"))).toBe(false);
expect(fs.existsSync(path.join(sub2, "bonus.zip"))).toBe(false);
expect(fs.existsSync(path.join(sub2, "bonus.7z"))).toBe(false);
// Non-archives kept
expect(fs.existsSync(path.join(sub1, "video.mkv"))).toBe(true);
expect(fs.existsSync(path.join(sub2, "subtitle.srt"))).toBe(true);
});
it("detects link artifacts by URL content in text files", async () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "rd-clean-"));
tempDirs.push(dir);
// File with link-like name containing URLs should be removed
fs.writeFileSync(path.join(dir, "download_links.txt"), "https://rapidgator.net/file/abc123\nhttps://uploaded.net/file/def456\n");
// File with link-like name but no URLs should be kept
fs.writeFileSync(path.join(dir, "my_downloads.txt"), "Just some random text without URLs");
// Regular text file that doesn't match the link pattern should be kept
fs.writeFileSync(path.join(dir, "readme.txt"), "https://example.com");
// .url files should always be removed
fs.writeFileSync(path.join(dir, "bookmark.url"), "[InternetShortcut]\nURL=https://example.com");
// .dlc files should always be removed
fs.writeFileSync(path.join(dir, "container.dlc"), "encrypted-data");
const removed = await removeDownloadLinkArtifacts(dir);
expect(removed).toBeGreaterThanOrEqual(3); // download_links.txt + bookmark.url + container.dlc
expect(fs.existsSync(path.join(dir, "download_links.txt"))).toBe(false);
expect(fs.existsSync(path.join(dir, "bookmark.url"))).toBe(false);
expect(fs.existsSync(path.join(dir, "container.dlc"))).toBe(false);
// Non-matching files should be kept
expect(fs.existsSync(path.join(dir, "readme.txt"))).toBe(true);
});
it("does not recurse into sample symlink or junction targets", async () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "rd-clean-"));
const external = fs.mkdtempSync(path.join(os.tmpdir(), "rd-clean-ext-"));
tempDirs.push(dir, external);
const outsideFile = path.join(external, "outside-sample.mkv");
fs.writeFileSync(outsideFile, "keep", "utf8");
const linkedSampleDir = path.join(dir, "sample");
const linkType: fs.symlink.Type = process.platform === "win32" ? "junction" : "dir";
fs.symlinkSync(external, linkedSampleDir, linkType);
const result = await removeSampleArtifacts(dir);
expect(result.files).toBe(0);
expect(fs.existsSync(outsideFile)).toBe(true);
});
});
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import { cleanupCancelledPackageArtifacts, removeDownloadLinkArtifacts, removeSampleArtifacts } from "../src/main/cleanup";
const tempDirs: string[] = [];
afterEach(() => {
for (const dir of tempDirs.splice(0)) {
fs.rmSync(dir, { recursive: true, force: true });
}
});
describe("cleanup", () => {
it("removes archive artifacts but keeps media", () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "rd-clean-"));
tempDirs.push(dir);
fs.writeFileSync(path.join(dir, "release.part1.rar"), "x");
fs.writeFileSync(path.join(dir, "movie.mkv"), "x");
const removed = cleanupCancelledPackageArtifacts(dir);
expect(removed).toBeGreaterThan(0);
expect(fs.existsSync(path.join(dir, "release.part1.rar"))).toBe(false);
expect(fs.existsSync(path.join(dir, "movie.mkv"))).toBe(true);
});
it("removes sample artifacts and link files", async () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "rd-clean-"));
tempDirs.push(dir);
fs.mkdirSync(path.join(dir, "Samples"), { recursive: true });
fs.writeFileSync(path.join(dir, "Samples", "demo-sample.mkv"), "x");
fs.writeFileSync(path.join(dir, "download_links.txt"), "https://example.com/a\n");
const links = await removeDownloadLinkArtifacts(dir);
const samples = await removeSampleArtifacts(dir);
expect(links).toBeGreaterThan(0);
expect(samples.files + samples.dirs).toBeGreaterThan(0);
});
it("cleans up archive files in nested directories", () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "rd-clean-"));
tempDirs.push(dir);
// Create nested directory structure with archive files
const sub1 = path.join(dir, "season1");
const sub2 = path.join(dir, "season1", "extras");
fs.mkdirSync(sub2, { recursive: true });
fs.writeFileSync(path.join(sub1, "episode.part1.rar"), "x");
fs.writeFileSync(path.join(sub1, "episode.part2.rar"), "x");
fs.writeFileSync(path.join(sub2, "bonus.zip"), "x");
fs.writeFileSync(path.join(sub2, "bonus.7z"), "x");
// Non-archive files should be kept
fs.writeFileSync(path.join(sub1, "video.mkv"), "real content");
fs.writeFileSync(path.join(sub2, "subtitle.srt"), "subtitle content");
const removed = cleanupCancelledPackageArtifacts(dir);
expect(removed).toBe(4); // 2 rar parts + zip + 7z
expect(fs.existsSync(path.join(sub1, "episode.part1.rar"))).toBe(false);
expect(fs.existsSync(path.join(sub1, "episode.part2.rar"))).toBe(false);
expect(fs.existsSync(path.join(sub2, "bonus.zip"))).toBe(false);
expect(fs.existsSync(path.join(sub2, "bonus.7z"))).toBe(false);
// Non-archives kept
expect(fs.existsSync(path.join(sub1, "video.mkv"))).toBe(true);
expect(fs.existsSync(path.join(sub2, "subtitle.srt"))).toBe(true);
});
it("detects link artifacts by URL content in text files", async () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "rd-clean-"));
tempDirs.push(dir);
// File with link-like name containing URLs should be removed
fs.writeFileSync(path.join(dir, "download_links.txt"), "https://rapidgator.net/file/abc123\nhttps://uploaded.net/file/def456\n");
// File with link-like name but no URLs should be kept
fs.writeFileSync(path.join(dir, "my_downloads.txt"), "Just some random text without URLs");
// Regular text file that doesn't match the link pattern should be kept
fs.writeFileSync(path.join(dir, "readme.txt"), "https://example.com");
// .url files should always be removed
fs.writeFileSync(path.join(dir, "bookmark.url"), "[InternetShortcut]\nURL=https://example.com");
// .dlc files should always be removed
fs.writeFileSync(path.join(dir, "container.dlc"), "encrypted-data");
const removed = await removeDownloadLinkArtifacts(dir);
expect(removed).toBeGreaterThanOrEqual(3); // download_links.txt + bookmark.url + container.dlc
expect(fs.existsSync(path.join(dir, "download_links.txt"))).toBe(false);
expect(fs.existsSync(path.join(dir, "bookmark.url"))).toBe(false);
expect(fs.existsSync(path.join(dir, "container.dlc"))).toBe(false);
// Non-matching files should be kept
expect(fs.existsSync(path.join(dir, "readme.txt"))).toBe(true);
});
it("does not recurse into sample symlink or junction targets", async () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "rd-clean-"));
const external = fs.mkdtempSync(path.join(os.tmpdir(), "rd-clean-ext-"));
tempDirs.push(dir, external);
const outsideFile = path.join(external, "outside-sample.mkv");
fs.writeFileSync(outsideFile, "keep", "utf8");
const linkedSampleDir = path.join(dir, "sample");
const linkType: fs.symlink.Type = process.platform === "win32" ? "junction" : "dir";
fs.symlinkSync(external, linkedSampleDir, linkType);
const result = await removeSampleArtifacts(dir);
expect(result.files).toBe(0);
expect(fs.existsSync(outsideFile)).toBe(true);
});
});
+998 -998
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+491 -491
View File
File diff suppressed because it is too large Load Diff
+546 -546
View File
File diff suppressed because it is too large Load Diff