alepha
Version:
Easy-to-use modern TypeScript framework for building many kind of applications.
592 lines (516 loc) • 20.7 kB
text/typescript
import { Readable } from "node:stream";
import { Alepha } from "alepha";
import { beforeEach, describe, expect, it } from "vitest";
import { AlephaSystem } from "../index.ts";
import { FileDetector } from "../services/FileDetector.ts";
/**
* Helper to create a readable stream from a buffer
*/
function createStream(buffer: Buffer): Readable {
return Readable.from(buffer);
}
/**
* Helper to read all data from a stream
*/
async function readStream(stream: Readable): Promise<Buffer> {
const chunks: Buffer[] = [];
for await (const chunk of stream) {
chunks.push(chunk);
}
return Buffer.concat(chunks);
}
describe("FileDetector", () => {
let detector: FileDetector;
beforeEach(() => {
const alepha = Alepha.create().with(AlephaSystem);
detector = alepha.inject(FileDetector);
});
describe("getContentType", () => {
it("should return correct MIME type for known extensions", () => {
// Document types
expect(detector.getContentType("test.json")).toBe("application/json");
expect(detector.getContentType("test.txt")).toBe("text/plain");
expect(detector.getContentType("test.html")).toBe("text/html");
expect(detector.getContentType("test.htm")).toBe("text/html");
expect(detector.getContentType("test.xml")).toBe("application/xml");
expect(detector.getContentType("test.csv")).toBe("text/csv");
expect(detector.getContentType("test.pdf")).toBe("application/pdf");
expect(detector.getContentType("test.md")).toBe("text/markdown");
expect(detector.getContentType("test.markdown")).toBe("text/markdown");
expect(detector.getContentType("test.rtf")).toBe("application/rtf");
// Stylesheet and scripts
expect(detector.getContentType("test.css")).toBe("text/css");
expect(detector.getContentType("test.js")).toBe("application/javascript");
expect(detector.getContentType("test.mjs")).toBe(
"application/javascript",
);
expect(detector.getContentType("test.ts")).toBe("application/typescript");
expect(detector.getContentType("test.jsx")).toBe("text/jsx");
expect(detector.getContentType("test.tsx")).toBe("text/tsx");
// Archive formats
expect(detector.getContentType("test.zip")).toBe("application/zip");
expect(detector.getContentType("test.rar")).toBe("application/vnd.rar");
expect(detector.getContentType("test.7z")).toBe(
"application/x-7z-compressed",
);
expect(detector.getContentType("test.tar")).toBe("application/x-tar");
expect(detector.getContentType("test.gz")).toBe("application/gzip");
expect(detector.getContentType("test.tar.gz")).toBe("application/gzip");
expect(detector.getContentType("test.tgz")).toBe("application/gzip");
// Image formats
expect(detector.getContentType("test.png")).toBe("image/png");
expect(detector.getContentType("test.jpg")).toBe("image/jpeg");
expect(detector.getContentType("test.jpeg")).toBe("image/jpeg");
expect(detector.getContentType("test.gif")).toBe("image/gif");
expect(detector.getContentType("test.webp")).toBe("image/webp");
expect(detector.getContentType("test.svg")).toBe("image/svg+xml");
expect(detector.getContentType("test.bmp")).toBe("image/bmp");
expect(detector.getContentType("test.ico")).toBe("image/x-icon");
expect(detector.getContentType("test.tiff")).toBe("image/tiff");
expect(detector.getContentType("test.tif")).toBe("image/tiff");
// Audio formats
expect(detector.getContentType("test.mp3")).toBe("audio/mpeg");
expect(detector.getContentType("test.wav")).toBe("audio/wav");
expect(detector.getContentType("test.ogg")).toBe("audio/ogg");
expect(detector.getContentType("test.m4a")).toBe("audio/mp4");
expect(detector.getContentType("test.aac")).toBe("audio/aac");
expect(detector.getContentType("test.flac")).toBe("audio/flac");
// Video formats
expect(detector.getContentType("test.mp4")).toBe("video/mp4");
expect(detector.getContentType("test.webm")).toBe("video/webm");
expect(detector.getContentType("test.avi")).toBe("video/x-msvideo");
expect(detector.getContentType("test.mov")).toBe("video/quicktime");
expect(detector.getContentType("test.wmv")).toBe("video/x-ms-wmv");
expect(detector.getContentType("test.flv")).toBe("video/x-flv");
expect(detector.getContentType("test.mkv")).toBe("video/x-matroska");
// Office documents
expect(detector.getContentType("test.doc")).toBe("application/msword");
expect(detector.getContentType("test.docx")).toBe(
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
);
expect(detector.getContentType("test.xls")).toBe(
"application/vnd.ms-excel",
);
expect(detector.getContentType("test.xlsx")).toBe(
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
);
expect(detector.getContentType("test.ppt")).toBe(
"application/vnd.ms-powerpoint",
);
expect(detector.getContentType("test.pptx")).toBe(
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
);
// Other formats
expect(detector.getContentType("test.woff")).toBe("font/woff");
expect(detector.getContentType("test.woff2")).toBe("font/woff2");
expect(detector.getContentType("test.ttf")).toBe("font/ttf");
expect(detector.getContentType("test.otf")).toBe("font/otf");
expect(detector.getContentType("test.eot")).toBe(
"application/vnd.ms-fontobject",
);
});
it("should return default MIME type for unknown extensions", () => {
expect(detector.getContentType("test.unknown")).toBe(
"application/octet-stream",
);
expect(detector.getContentType("test")).toBe("application/octet-stream");
expect(detector.getContentType("")).toBe("application/octet-stream");
});
it("should handle filenames with multiple dots", () => {
expect(detector.getContentType("test.backup.json")).toBe(
"application/json",
);
expect(detector.getContentType("my.file.name.txt")).toBe("text/plain");
});
});
describe("detectFileType", () => {
describe("Image formats", () => {
it("should detect PNG from magic bytes", async () => {
const pngMagic = Buffer.from([
0x89,
0x50,
0x4e,
0x47,
0x0d,
0x0a,
0x1a,
0x0a,
...Array(8).fill(0),
]);
const stream = createStream(pngMagic);
const result = await detector.detectFileType(stream, "image.png");
expect(result.mimeType).toBe("image/png");
expect(result.extension).toBe("png");
expect(result.verified).toBe(true);
// Verify stream is still readable
const data = await readStream(result.stream);
expect(data).toEqual(pngMagic);
});
it("should detect JPEG from magic bytes", async () => {
const jpegMagic = Buffer.from([
0xff,
0xd8,
0xff,
0xe0,
...Array(12).fill(0),
]);
const stream = createStream(jpegMagic);
const result = await detector.detectFileType(stream, "photo.jpg");
expect(result.mimeType).toBe("image/jpeg");
expect(result.extension).toBe("jpg");
expect(result.verified).toBe(true);
const data = await readStream(result.stream);
expect(data).toEqual(jpegMagic);
});
it("should detect GIF from magic bytes", async () => {
const gifMagic = Buffer.from([
0x47,
0x49,
0x46,
0x38,
0x39,
0x61,
...Array(10).fill(0),
]);
const stream = createStream(gifMagic);
const result = await detector.detectFileType(stream, "animation.gif");
expect(result.mimeType).toBe("image/gif");
expect(result.extension).toBe("gif");
expect(result.verified).toBe(true);
const data = await readStream(result.stream);
expect(data).toEqual(gifMagic);
});
it("should detect WebP from magic bytes", async () => {
const webpMagic = Buffer.from([
0x52,
0x49,
0x46,
0x46,
0x00,
0x00,
0x00,
0x00,
0x57,
0x45,
0x42,
0x50,
...Array(4).fill(0),
]);
const stream = createStream(webpMagic);
const result = await detector.detectFileType(stream, "image.webp");
expect(result.mimeType).toBe("image/webp");
expect(result.extension).toBe("webp");
expect(result.verified).toBe(true);
const data = await readStream(result.stream);
expect(data).toEqual(webpMagic);
});
it("should detect BMP from magic bytes", async () => {
const bmpMagic = Buffer.from([0x42, 0x4d, ...Array(14).fill(0)]);
const stream = createStream(bmpMagic);
const result = await detector.detectFileType(stream, "image.bmp");
expect(result.mimeType).toBe("image/bmp");
expect(result.extension).toBe("bmp");
expect(result.verified).toBe(true);
});
});
describe("Document formats", () => {
it("should detect PDF from magic bytes", async () => {
const pdfMagic = Buffer.from([
0x25,
0x50,
0x44,
0x46,
0x2d,
...Array(11).fill(0),
]);
const stream = createStream(pdfMagic);
const result = await detector.detectFileType(stream, "document.pdf");
expect(result.mimeType).toBe("application/pdf");
expect(result.extension).toBe("pdf");
expect(result.verified).toBe(true);
const data = await readStream(result.stream);
expect(data).toEqual(pdfMagic);
});
it("should detect ZIP from magic bytes", async () => {
const zipMagic = Buffer.from([
0x50,
0x4b,
0x03,
0x04,
...Array(12).fill(0),
]);
const stream = createStream(zipMagic);
const result = await detector.detectFileType(stream, "archive.zip");
expect(result.mimeType).toBe("application/zip");
expect(result.extension).toBe("zip");
expect(result.verified).toBe(true);
});
});
describe("Archive formats", () => {
it("should detect RAR from magic bytes", async () => {
const rarMagic = Buffer.from([
0x52,
0x61,
0x72,
0x21,
0x1a,
0x07,
...Array(10).fill(0),
]);
const stream = createStream(rarMagic);
const result = await detector.detectFileType(stream, "archive.rar");
expect(result.mimeType).toBe("application/vnd.rar");
expect(result.extension).toBe("rar");
expect(result.verified).toBe(true);
});
it("should detect 7z from magic bytes", async () => {
const sevenZipMagic = Buffer.from([
0x37,
0x7a,
0xbc,
0xaf,
0x27,
0x1c,
...Array(10).fill(0),
]);
const stream = createStream(sevenZipMagic);
const result = await detector.detectFileType(stream, "archive.7z");
expect(result.mimeType).toBe("application/x-7z-compressed");
expect(result.extension).toBe("7z");
expect(result.verified).toBe(true);
});
it("should detect GZIP from magic bytes", async () => {
const gzipMagic = Buffer.from([0x1f, 0x8b, ...Array(14).fill(0)]);
const stream = createStream(gzipMagic);
const result = await detector.detectFileType(stream, "archive.gz");
expect(result.mimeType).toBe("application/gzip");
expect(result.extension).toBe("gz");
expect(result.verified).toBe(true);
});
});
describe("Audio formats", () => {
it("should detect MP3 from magic bytes", async () => {
const mp3Magic = Buffer.from([0xff, 0xfb, ...Array(14).fill(0)]);
const stream = createStream(mp3Magic);
const result = await detector.detectFileType(stream, "song.mp3");
expect(result.mimeType).toBe("audio/mpeg");
expect(result.extension).toBe("mp3");
expect(result.verified).toBe(true);
});
it("should detect WAV from magic bytes", async () => {
const wavMagic = Buffer.from([
0x52,
0x49,
0x46,
0x46,
0x00,
0x00,
0x00,
0x00,
0x57,
0x41,
0x56,
0x45,
...Array(4).fill(0),
]);
const stream = createStream(wavMagic);
const result = await detector.detectFileType(stream, "audio.wav");
expect(result.mimeType).toBe("audio/wav");
expect(result.extension).toBe("wav");
expect(result.verified).toBe(true);
});
it("should detect FLAC from magic bytes", async () => {
const flacMagic = Buffer.from([
0x66,
0x4c,
0x61,
0x43,
...Array(12).fill(0),
]);
const stream = createStream(flacMagic);
const result = await detector.detectFileType(stream, "audio.flac");
expect(result.mimeType).toBe("audio/flac");
expect(result.extension).toBe("flac");
expect(result.verified).toBe(true);
});
});
describe("Mismatched extension and content", () => {
it("should detect actual PNG even when extension is .txt", async () => {
const pngMagic = Buffer.from([
0x89,
0x50,
0x4e,
0x47,
0x0d,
0x0a,
0x1a,
0x0a,
...Array(8).fill(0),
]);
const stream = createStream(pngMagic);
const result = await detector.detectFileType(stream, "file.txt");
expect(result.mimeType).toBe("image/png");
expect(result.extension).toBe("png");
expect(result.verified).toBe(true);
});
it("should detect actual JPEG even when extension is .pdf", async () => {
const jpegMagic = Buffer.from([
0xff,
0xd8,
0xff,
0xe1,
...Array(12).fill(0),
]);
const stream = createStream(jpegMagic);
const result = await detector.detectFileType(stream, "document.pdf");
expect(result.mimeType).toBe("image/jpeg");
expect(result.extension).toBe("jpg"); // Matches jpg signature first
expect(result.verified).toBe(true);
});
it("should detect actual PDF even when extension is .jpg", async () => {
const pdfMagic = Buffer.from([
0x25,
0x50,
0x44,
0x46,
0x2d,
...Array(11).fill(0),
]);
const stream = createStream(pdfMagic);
const result = await detector.detectFileType(stream, "file.jpg");
expect(result.mimeType).toBe("application/pdf");
expect(result.extension).toBe("pdf");
expect(result.verified).toBe(true);
});
});
describe("Fallback to extension-based detection", () => {
it("should fall back to extension for text files without magic bytes", async () => {
const textContent = Buffer.from("Hello, world!", "utf-8");
const stream = createStream(textContent);
const result = await detector.detectFileType(stream, "file.txt");
expect(result.mimeType).toBe("text/plain");
expect(result.extension).toBe("txt");
expect(result.verified).toBe(false);
const data = await readStream(result.stream);
expect(data.toString("utf-8")).toBe("Hello, world!");
});
it("should fall back to extension for JSON files", async () => {
const jsonContent = Buffer.from('{"key": "value"}', "utf-8");
const stream = createStream(jsonContent);
const result = await detector.detectFileType(stream, "data.json");
expect(result.mimeType).toBe("application/json");
expect(result.extension).toBe("json");
expect(result.verified).toBe(false);
});
it("should return octet-stream for unknown files without magic bytes", async () => {
const randomContent = Buffer.from([0x01, 0x02, 0x03, 0x04]);
const stream = createStream(randomContent);
const result = await detector.detectFileType(stream, "file.unknown");
expect(result.mimeType).toBe("application/octet-stream");
expect(result.extension).toBe("unknown");
expect(result.verified).toBe(false);
});
});
describe("Edge cases", () => {
it("should handle empty streams", async () => {
const emptyBuffer = Buffer.from([]);
const stream = createStream(emptyBuffer);
const result = await detector.detectFileType(stream, "empty.txt");
expect(result.mimeType).toBe("text/plain");
expect(result.extension).toBe("txt");
expect(result.verified).toBe(false);
});
it("should handle streams with less than 16 bytes", async () => {
const shortBuffer = Buffer.from([0x89, 0x50, 0x4e, 0x47]);
const stream = createStream(shortBuffer);
const result = await detector.detectFileType(stream, "incomplete.png");
// Should not verify because signature is incomplete
expect(result.verified).toBe(false);
});
it("should handle filenames without extensions", async () => {
const randomContent = Buffer.from([0x01, 0x02, 0x03]);
const stream = createStream(randomContent);
const result = await detector.detectFileType(stream, "README");
expect(result.mimeType).toBe("application/octet-stream");
expect(result.extension).toBe("");
expect(result.verified).toBe(false);
});
it("should preserve all stream data", async () => {
const longContent = Buffer.concat([
Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]), // PNG header
Buffer.from("This is some additional data after the header"),
]);
const stream = createStream(longContent);
const result = await detector.detectFileType(stream, "image.png");
expect(result.verified).toBe(true);
// Verify all data is preserved
const data = await readStream(result.stream);
expect(data).toEqual(longContent);
});
});
describe("Multiple JPEG variants", () => {
it("should detect JPEG with 0xFFE1 marker", async () => {
const jpegMagic = Buffer.from([
0xff,
0xd8,
0xff,
0xe1,
...Array(12).fill(0),
]);
const stream = createStream(jpegMagic);
const result = await detector.detectFileType(stream, "photo.jpeg");
expect(result.mimeType).toBe("image/jpeg");
expect(result.verified).toBe(true);
});
it("should detect JPEG with 0xFFE2 marker", async () => {
const jpegMagic = Buffer.from([
0xff,
0xd8,
0xff,
0xe2,
...Array(12).fill(0),
]);
const stream = createStream(jpegMagic);
const result = await detector.detectFileType(stream, "photo.jpg");
expect(result.mimeType).toBe("image/jpeg");
expect(result.verified).toBe(true);
});
});
describe("Office formats", () => {
it("should detect old Office format (DOC)", async () => {
const docMagic = Buffer.from([
0xd0,
0xcf,
0x11,
0xe0,
0xa1,
0xb1,
0x1a,
0xe1,
...Array(8).fill(0),
]);
const stream = createStream(docMagic);
const result = await detector.detectFileType(stream, "document.doc");
expect(result.mimeType).toBe("application/msword");
expect(result.extension).toBe("doc");
expect(result.verified).toBe(true);
});
it("should detect DOCX (ZIP-based)", async () => {
const docxMagic = Buffer.from([
0x50,
0x4b,
0x03,
0x04,
...Array(12).fill(0),
]);
const stream = createStream(docxMagic);
const result = await detector.detectFileType(stream, "document.docx");
expect(result.mimeType).toBe(
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
);
expect(result.extension).toBe("docx");
expect(result.verified).toBe(true);
});
});
});
});