import { readFile } from "node:fs/promises"; import { inflateRawSync } from "node:zlib"; import { describe, expect, it } from "vitest"; /** Mirrors hub/src/capability/pbank.ts zip reader contracts. */ function listZipEntries(buffer: Buffer) { let eocd = -1; const minEocd = Math.max(0, buffer.length - (22 + 0xffff)); for (let i = buffer.length - 22; i >= minEocd; i -= 1) { if (buffer.readUInt32LE(i) === 0x06054b50) { eocd = i; break; } } if (eocd < 0) throw new Error("missing eocd"); const totalEntries = buffer.readUInt16LE(eocd + 10); const centralOffset = buffer.readUInt32LE(eocd + 16); const entries: Array<{ name: string; method: number; compressedSize: number; uncompressedSize: number; localHeaderOffset: number }> = []; let offset = centralOffset; for (let i = 0; i < totalEntries; i += 1) { const method = buffer.readUInt16LE(offset + 10); const compressedSize = buffer.readUInt32LE(offset + 20); const uncompressedSize = buffer.readUInt32LE(offset + 24); const nameLen = buffer.readUInt16LE(offset + 28); const extraLen = buffer.readUInt16LE(offset + 30); const commentLen = buffer.readUInt16LE(offset + 32); const localHeaderOffset = buffer.readUInt32LE(offset + 42); const nameStart = offset + 46; const name = buffer.subarray(nameStart, nameStart + nameLen).toString("utf8"); entries.push({ name, method, compressedSize, uncompressedSize, localHeaderOffset }); offset = nameStart + nameLen + extraLen + commentLen; } return entries; } function inflateZipEntry(buffer: Buffer, entry: { method: number; compressedSize: number; uncompressedSize: number; localHeaderOffset: number }) { const local = entry.localHeaderOffset; const nameLen = buffer.readUInt16LE(local + 26); const extraLen = buffer.readUInt16LE(local + 28); const dataStart = local + 30 + nameLen + extraLen; const compressed = buffer.subarray(dataStart, dataStart + entry.compressedSize); if (entry.method === 0) return Buffer.from(compressed); if (entry.method === 8) return Buffer.from(inflateRawSync(compressed)); throw new Error(`method ${entry.method}`); } describe("pbank zip reader contract", () => { it("lists and inflates deflated entries without host unzip", async () => { const zip = await readFile(new URL("./fixtures/pbank-mini.zip", import.meta.url)); const entries = listZipEntries(zip); expect(entries.map((e) => e.name).sort()).toEqual(["fig/a.png", "hello.txt"]); const hello = entries.find((e) => e.name === "hello.txt")!; const text = inflateZipEntry(zip, hello).toString("utf8"); expect(text).toBe("hello pbank\n"); }); });