79 lines
3.1 KiB
TypeScript
79 lines
3.1 KiB
TypeScript
import { describe, test, expect } from 'bun:test';
|
|
import { extractFirstFileFromTar } from '../src/lib/server/tar-extract';
|
|
|
|
// Hand-roll a minimal USTAR tar containing a single regular file.
|
|
// Mirrors the writer in docker.ts:writeContainerFile (which Dockhand already
|
|
// uses against Docker's archive API), so the parser is exercised against the
|
|
// exact format the production code emits.
|
|
function makeTar(filename: string, content: Uint8Array): Uint8Array {
|
|
const fileSize = content.length;
|
|
const paddedContentSize = Math.ceil(fileSize / 512) * 512;
|
|
const totalSize = 512 + paddedContentSize + 1024;
|
|
const tar = new Uint8Array(totalSize);
|
|
const enc = new TextEncoder();
|
|
|
|
tar.set(enc.encode(filename).slice(0, 100), 0);
|
|
tar.set(enc.encode('0000644\0'), 100);
|
|
tar.set(enc.encode('0000000\0'), 108);
|
|
tar.set(enc.encode('0000000\0'), 116);
|
|
tar.set(enc.encode(fileSize.toString(8).padStart(11, '0') + '\0'), 124);
|
|
tar.set(enc.encode('0'.repeat(11) + '\0'), 136);
|
|
tar.set(enc.encode(' '), 148); // checksum placeholder (spaces)
|
|
tar[156] = 0x30; // typeflag '0' = regular file
|
|
tar.set(enc.encode('ustar\0'), 257);
|
|
tar.set(enc.encode('00'), 263);
|
|
|
|
// Checksum: sum of first 512 bytes with checksum field treated as spaces
|
|
let cs = 0;
|
|
for (let i = 0; i < 512; i++) cs += tar[i];
|
|
tar.set(enc.encode(cs.toString(8).padStart(6, '0') + '\0 '), 148);
|
|
|
|
tar.set(content, 512);
|
|
return tar;
|
|
}
|
|
|
|
describe('extractFirstFileFromTar', () => {
|
|
test('returns the bytes of a single text file', () => {
|
|
const content = new TextEncoder().encode('hello world\n');
|
|
const tar = makeTar('greeting.txt', content);
|
|
const out = extractFirstFileFromTar(tar);
|
|
expect(new TextDecoder().decode(out)).toBe('hello world\n');
|
|
});
|
|
|
|
test('round-trips arbitrary binary bytes including NULs', () => {
|
|
const content = new Uint8Array([0x00, 0xff, 0x42, 0x00, 0x01, 0x7f, 0x80]);
|
|
const tar = makeTar('blob.bin', content);
|
|
const out = extractFirstFileFromTar(tar);
|
|
expect(Array.from(out)).toEqual(Array.from(content));
|
|
});
|
|
|
|
test('handles a file whose size is not a multiple of 512', () => {
|
|
// 600 bytes — pads to 1024 in the tar but the extracted slice must be exactly 600
|
|
const content = new Uint8Array(600).fill(0xab);
|
|
const tar = makeTar('odd.bin', content);
|
|
const out = extractFirstFileFromTar(tar);
|
|
expect(out.length).toBe(600);
|
|
expect(out.every((b) => b === 0xab)).toBe(true);
|
|
});
|
|
|
|
test('handles an empty file', () => {
|
|
const tar = makeTar('empty.txt', new Uint8Array(0));
|
|
const out = extractFirstFileFromTar(tar);
|
|
expect(out.length).toBe(0);
|
|
});
|
|
|
|
test('throws when the tar contains no regular file', () => {
|
|
// All-zero block looks like end-of-archive — no entries at all
|
|
const tar = new Uint8Array(1024);
|
|
expect(() => extractFirstFileFromTar(tar)).toThrow('No regular file');
|
|
});
|
|
|
|
test('throws on a truncated tar', () => {
|
|
const content = new TextEncoder().encode('this content is missing');
|
|
const tar = makeTar('truncated.txt', content);
|
|
// Lop off the content — leave just the header
|
|
const truncated = tar.slice(0, 512);
|
|
expect(() => extractFirstFileFromTar(truncated)).toThrow();
|
|
});
|
|
});
|