const test = require('node:test'); const assert = require('node:assert/strict'); const crypto = require('crypto'); const fs = require('fs'); const os = require('os'); const path = require('path'); process.env.EXTRACT_DIR = fs.mkdtempSync(path.join(os.tmpdir(), 'printhive-extract-')); const archives = require('../lib/archives'); const fixture = (name) => path.join(__dirname, 'fixtures', name); const sha = (p) => crypto.createHash('sha256').update(fs.readFileSync(p)).digest('hex'); const clean = (p) => p.replace(/^\.\//, ''); // Each pack holds models/cube.stl, models/inner.3mf and readme.txt, plus macOS // junk (._ files, __MACOSX, .DS_Store); the zip also has a ../ path. const EXPECTED = ['models/cube.stl', 'models/inner.3mf', 'readme.txt']; test.after(() => fs.rmSync(process.env.EXTRACT_DIR, { recursive: true, force: true })); for (const [name, format] of [['pack.zip', 'zip'], ['pack.7z', '7z'], ['pack.tar.gz', 'tgz']]) { const file = { id: `test-${format}`, file_path: fixture(name), format }; test(`${name}: lists real files, skipping folders, junk and unsafe paths`, async () => { const entries = await archives.listEntries(file); assert.deepEqual(entries.map((e) => clean(e.path)).sort(), EXPECTED); const cube = entries.find((e) => clean(e.path) === 'models/cube.stl'); assert.equal(cube.size, fs.statSync(fixture('cube.stl')).size); }); test(`${name}: extracts one entry byte-for-byte, and caches it`, async () => { const entries = await archives.listEntries(file); const cube = { ...entries.find((e) => clean(e.path) === 'models/cube.stl'), id: 1 }; const abs = await archives.extractEntry(file, cube); assert.equal(sha(abs), sha(fixture('cube.stl'))); assert.equal(await archives.extractEntry(file, cube), abs); }); } test('a missing entry fails clearly instead of returning a path', async () => { const file = { id: 'test-missing', file_path: fixture('pack.zip'), format: 'zip' }; await assert.rejects(archives.extractEntry(file, { id: 2, path: 'models/nope.stl' }), /Could not extract/); }); test('an unreadable archive rejects', async () => { await assert.rejects(archives.listEntries({ file_path: fixture('cube.stl'), format: 'zip' })); });