Files
convert/test/mime.test.js
T

218 lines
8.5 KiB
JavaScript

import { describe, it, expect } from 'vitest';
import path from 'node:path';
import fs from 'node:fs/promises';
import { detectInputMime, outputMimeType, resolveInputFormat } from '../src/mime.js';
describe('detectInputMime', () => {
it('detects PNG from magic bytes regardless of file extension', async () => {
const fixturePath = path.join(import.meta.dirname, 'fixtures', 'sample.png');
const result = await detectInputMime(fixturePath);
expect(result).toEqual({ ext: 'png', mime: 'image/png' });
});
});
describe('outputMimeType', () => {
it('returns the MIME type for a known target format', () => {
expect(outputMimeType('pdf')).toBe('application/pdf');
expect(outputMimeType('png')).toBe('image/png');
expect(outputMimeType('docx')).toBe(
'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
);
expect(outputMimeType('md')).toBe('text/markdown');
});
it('throws for an unknown target format', () => {
expect(() => outputMimeType('made-up-format')).toThrowError(/made-up-format/);
});
});
describe('outputMimeType — ico', () => {
it('returns image/x-icon for ico', () => {
expect(outputMimeType('ico')).toBe('image/x-icon');
});
});
describe('resolveInputFormat', () => {
it('accepts a PNG file declared as png', async () => {
const fixturePath = path.join(import.meta.dirname, 'fixtures', 'sample.png');
const result = await resolveInputFormat(fixturePath, 'png');
expect(result).toEqual({ mime: 'image/png', valid: true });
});
it('rejects a PNG file declared as a different format', async () => {
const fixturePath = path.join(import.meta.dirname, 'fixtures', 'sample.png');
const result = await resolveInputFormat(fixturePath, 'pdf');
expect(result.valid).toBe(false);
});
it('treats jpg and jpeg as equivalent declared formats', async () => {
const fixturePath = path.join(import.meta.dirname, 'fixtures', 'sample.png');
const result = await resolveInputFormat(fixturePath, 'png');
expect(result.valid).toBe(true);
});
it('trusts the declared format for undetectable txt files', async () => {
const fixturePath = path.join(import.meta.dirname, 'fixtures', 'sample.txt');
await fs.writeFile(fixturePath, 'plain text, no magic bytes');
const result = await resolveInputFormat(fixturePath, 'txt');
expect(result).toEqual({ mime: 'text/plain', valid: true });
await fs.unlink(fixturePath);
});
it('trusts the declared format for undetectable md files', async () => {
const fixturePath = path.join(import.meta.dirname, 'fixtures', 'sample.md');
await fs.writeFile(fixturePath, '# plain markdown, no magic bytes');
const result = await resolveInputFormat(fixturePath, 'md');
expect(result).toEqual({ mime: 'text/markdown', valid: true });
await fs.unlink(fixturePath);
});
it('rejects an undetectable file declared as a binary format', async () => {
const fixturePath = path.join(import.meta.dirname, 'fixtures', 'sample-fake.png');
await fs.writeFile(fixturePath, 'this is not really a PNG');
const result = await resolveInputFormat(fixturePath, 'png');
expect(result.valid).toBe(false);
await fs.unlink(fixturePath);
});
});
describe('resolveInputFormat — heic/heif alias', () => {
it('accepts a HEIC-family file declared as heic', async () => {
const fixturePath = path.join(import.meta.dirname, 'fixtures', 'sample.heic');
const result = await resolveInputFormat(fixturePath, 'heic');
expect(result.valid).toBe(true);
});
it('accepts the same HEIC-family content declared as heif', async () => {
const fixturePath = path.join(import.meta.dirname, 'fixtures', 'sample.heif');
const result = await resolveInputFormat(fixturePath, 'heif');
expect(result.valid).toBe(true);
});
});
describe('outputMimeType — fonts', () => {
it('returns the correct MIME type for each font target format', () => {
expect(outputMimeType('ttf')).toBe('font/ttf');
expect(outputMimeType('otf')).toBe('font/otf');
expect(outputMimeType('woff')).toBe('font/woff');
});
});
describe('outputMimeType — ebooks', () => {
it('returns the correct MIME type for each ebook target format', () => {
expect(outputMimeType('epub')).toBe('application/epub+zip');
expect(outputMimeType('fb2')).toBe('application/x-fictionbook+xml');
expect(outputMimeType('mobi')).toBe('application/x-mobipocket-ebook');
expect(outputMimeType('azw3')).toBe('application/vnd.amazon.ebook');
expect(outputMimeType('pdb')).toBe('application/vnd.palm');
expect(outputMimeType('lrf')).toBe('application/octet-stream');
expect(outputMimeType('rb')).toBe('application/octet-stream');
expect(outputMimeType('snb')).toBe('application/octet-stream');
expect(outputMimeType('tcr')).toBe('application/octet-stream');
});
});
describe('resolveInputFormat — azw3/mobi collision', () => {
function buildMobiFamilyBuffer() {
// PDB header: 8-byte "BOOKMOBI" type+creator magic at offset 60, shared by both
// real .mobi and real .azw3 files (confirmed in file-type's source).
const buffer = Buffer.alloc(68);
buffer.write('BOOKMOBI', 60, 'ascii');
return buffer;
}
it('confirms file-type reports mobi-family content as ext "mobi" regardless of which of the two formats it is (documents the collision this fix works around)', async () => {
const fixturePath = path.join(import.meta.dirname, 'fixtures', 'sample-mobi-family.bin');
await fs.writeFile(fixturePath, buildMobiFamilyBuffer());
const detected = await detectInputMime(fixturePath);
expect(detected).toEqual({ ext: 'mobi', mime: 'application/x-mobipocket-ebook' });
await fs.unlink(fixturePath);
});
it('accepts a real mobi-family file declared as mobi', async () => {
const fixturePath = path.join(import.meta.dirname, 'fixtures', 'sample-mobi-family.bin');
await fs.writeFile(fixturePath, buildMobiFamilyBuffer());
const result = await resolveInputFormat(fixturePath, 'mobi');
expect(result).toEqual({ mime: 'application/x-mobipocket-ebook', valid: true });
await fs.unlink(fixturePath);
});
it('accepts a real mobi-family file declared as azw3 despite file-type reporting it as mobi', async () => {
const fixturePath = path.join(import.meta.dirname, 'fixtures', 'sample-mobi-family.bin');
await fs.writeFile(fixturePath, buildMobiFamilyBuffer());
const result = await resolveInputFormat(fixturePath, 'azw3');
expect(result).toEqual({ mime: 'application/x-mobipocket-ebook', valid: true });
await fs.unlink(fixturePath);
});
});
describe('resolveInputFormat — undetectable ebook formats', () => {
const undetectableFormats = {
fb2: 'application/x-fictionbook+xml',
lrf: 'application/octet-stream',
pdb: 'application/vnd.palm',
rb: 'application/octet-stream',
snb: 'application/octet-stream',
tcr: 'application/octet-stream',
};
it.each(Object.entries(undetectableFormats))(
'trusts the declared format for undetectable %s files',
async (declaredFormat, expectedMime) => {
const fixturePath = path.join(import.meta.dirname, 'fixtures', `sample.${declaredFormat}`);
await fs.writeFile(fixturePath, 'arbitrary bytes with no recognizable magic number');
const result = await resolveInputFormat(fixturePath, declaredFormat);
expect(result).toEqual({ mime: expectedMime, valid: true });
await fs.unlink(fixturePath);
}
);
});
describe('resolveInputFormat — dfont', () => {
it('confirms file-type alone misidentifies the dfont fixture as ico (documents the bug this fix works around)', async () => {
const fixturePath = path.join(import.meta.dirname, 'fixtures', 'sample.dfont');
const detected = await detectInputMime(fixturePath);
expect(detected.ext).toBe('ico');
});
it('accepts a real dfont file despite that misidentification', async () => {
const fixturePath = path.join(import.meta.dirname, 'fixtures', 'sample.dfont');
const result = await resolveInputFormat(fixturePath, 'dfont');
expect(result).toEqual({ mime: 'application/x-dfont', valid: true });
});
it('rejects content starting with the colliding magic bytes that is not a real dfont', async () => {
const fixturePath = path.join(import.meta.dirname, 'fixtures', 'sample-fake.dfont');
await fs.writeFile(fixturePath, Buffer.from([0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x10, 0x10]));
const result = await resolveInputFormat(fixturePath, 'dfont');
expect(result).toEqual({ mime: null, valid: false });
await fs.unlink(fixturePath);
});
});