diff --git a/worker/src/archive/detect.test.ts b/worker/src/archive/detect.test.ts new file mode 100644 index 0000000..49e627e --- /dev/null +++ b/worker/src/archive/detect.test.ts @@ -0,0 +1,39 @@ +import { describe, it, expect } from "vitest"; +import { detectArchive, isArchiveAttachment } from "./detect.js"; + +describe("detectArchive — 7z numbered multipart (pack.7z.001, pack.7z.002, ...)", () => { + it("recognizes a 7z multipart part as an archive attachment", () => { + expect(isArchiveAttachment("Lost Adventures Vol2.7z.001")).toBe(true); + }); + + it("extracts format, baseName, and partNumber", () => { + const info = detectArchive("Lost Adventures Vol2.7z.001"); + expect(info).toEqual({ + baseName: "Lost Adventures Vol2.7z", + partNumber: 1, + format: "7Z", + pattern: "SEVENZ_NUMBERED", + }); + }); + + it("groups multiple parts under the same baseName + format key regardless of part number", () => { + const part1 = detectArchive("Lost Adventures Vol2.7z.001"); + const part2 = detectArchive("Lost Adventures Vol2.7z.010"); + expect(part1?.baseName).toBe(part2?.baseName); + expect(part1?.format).toBe(part2?.format); + expect(part2?.partNumber).toBe(10); + }); + + it("is case-insensitive on the .7z extension", () => { + expect(detectArchive("Archive.7Z.002")?.format).toBe("7Z"); + }); + + it("still recognizes a standalone single .7z file", () => { + expect(detectArchive("Single Pack.7z")).toEqual({ + baseName: "Single Pack", + partNumber: -1, + format: "7Z", + pattern: "SINGLE", + }); + }); +}); diff --git a/worker/src/archive/detect.ts b/worker/src/archive/detect.ts index f4a0c7a..2fe5853 100644 --- a/worker/src/archive/detect.ts +++ b/worker/src/archive/detect.ts @@ -4,7 +4,7 @@ export interface MultipartInfo { baseName: string; partNumber: number; format: ArchiveFormat; - pattern: "ZIP_NUMBERED" | "ZIP_LEGACY" | "RAR_PART" | "RAR_LEGACY" | "SINGLE"; + pattern: "ZIP_NUMBERED" | "ZIP_LEGACY" | "RAR_PART" | "RAR_LEGACY" | "SEVENZ_NUMBERED" | "SINGLE"; } const patterns: { @@ -46,6 +46,14 @@ const patterns: { getBaseName: (m) => m[1], getPartNumber: (m) => parseInt(m[2], 10), }, + // pack.7z.001, pack.7z.002 (native 7z volume split) + { + regex: /^(.+\.7z)\.(\d{3,})$/i, + format: "7Z", + pattern: "SEVENZ_NUMBERED", + getBaseName: (m) => m[1], + getPartNumber: (m) => parseInt(m[2], 10), + }, ]; /** Extensions we recognize as fetchable documents (archives + standalone files) */