78 lines
4.6 KiB
TypeScript
78 lines
4.6 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import * as XLSX from 'xlsx';
|
|
import type { ImportSource } from '../../src/domain/import-source';
|
|
import { importSource } from '../../src/importers/engine';
|
|
import { detectImportFormat } from '../../src/importers/format-detection';
|
|
import { genericProfile } from '../../src/profiles/generic/generic-profile';
|
|
import { browserImportRuntimeAdapters } from '../../src/platform/browser/import-runtime-adapters';
|
|
|
|
function textSource(name: string, text: string, mediaType?: string): ImportSource {
|
|
const bytes = new TextEncoder().encode(text);
|
|
return { metadata: { name, sizeBytes: bytes.length, lastModified: 0, mediaType }, bytes };
|
|
}
|
|
|
|
const options = { profiles: [genericProfile] };
|
|
|
|
describe('format and container detection', () => {
|
|
it('prefers JSON structure over conflicting extension and media type hints', async () => {
|
|
const source = textSource('synthetic.csv', '{"title":"Synthetic"}', 'text/csv');
|
|
const detection = detectImportFormat(source, new TextDecoder().decode(source.bytes));
|
|
|
|
expect(detection).toMatchObject({ selected: 'JSON', structurallyValidated: true });
|
|
expect(detection?.conflicts.map(item => item.signal)).toEqual(expect.arrayContaining(['extension', 'media-type']));
|
|
|
|
const imported = await importSource(source, options, browserImportRuntimeAdapters);
|
|
expect(imported.format).toBe('JSON');
|
|
expect(imported.intermediate?.formatDetection).toMatchObject({ selected: 'JSON' });
|
|
expect(imported.intermediate?.diagnostics).toContainEqual(expect.objectContaining({ code: 'format-evidence-conflict' }));
|
|
});
|
|
|
|
it('uses an Excel workbook signature even when the file is mislabeled as CSV', async () => {
|
|
const workbook = XLSX.utils.book_new();
|
|
XLSX.utils.book_append_sheet(workbook, XLSX.utils.aoa_to_sheet([['Title'], ['Synthetic']]), 'Findings');
|
|
const bytes = new Uint8Array(XLSX.write(workbook, { type: 'array', bookType: 'xlsx' }));
|
|
const source: ImportSource = { metadata: { name: 'synthetic.csv', sizeBytes: bytes.length, lastModified: 0, mediaType: 'text/csv' }, bytes };
|
|
|
|
const imported = await importSource(source, options, browserImportRuntimeAdapters);
|
|
expect(imported.format).toBe('Excel');
|
|
expect(imported.intermediate?.formatDetection).toMatchObject({ selected: 'Excel', containerKind: 'excel-zip', structurallyValidated: true });
|
|
});
|
|
|
|
it('rejects generic ZIP, unsupported binary and fake Excel inputs with structured errors', async () => {
|
|
const damagedZip = new Uint8Array([0x50, 0x4b, 0x03, 0x04, 1, 2, 3]);
|
|
const damagedZipSource: ImportSource = { metadata: { name: 'spoofed.csv', sizeBytes: damagedZip.length, lastModified: 0 }, bytes: damagedZip };
|
|
await expect(importSource(damagedZipSource, options, browserImportRuntimeAdapters)).rejects.toMatchObject({
|
|
diagnostics: [expect.objectContaining({ code: 'zip-directory-missing' })]
|
|
});
|
|
const emptyZip = new Uint8Array([0x50, 0x4b, 0x05, 0x06, ...new Array(18).fill(0)]);
|
|
const zipSource: ImportSource = { metadata: { name: 'archive.zip', sizeBytes: emptyZip.length, lastModified: 0 }, bytes: emptyZip };
|
|
await expect(importSource(zipSource, options, browserImportRuntimeAdapters)).rejects.toMatchObject({
|
|
diagnostics: [expect.objectContaining({ code: 'unsupported-container' })]
|
|
});
|
|
|
|
const binary = new Uint8Array([0, 1, 2, 3, 4]);
|
|
const binarySource: ImportSource = { metadata: { name: 'synthetic.csv', sizeBytes: binary.length, lastModified: 0 }, bytes: binary };
|
|
await expect(importSource(binarySource, options, browserImportRuntimeAdapters)).rejects.toMatchObject({
|
|
diagnostics: [expect.objectContaining({ code: 'unsupported-binary-format' })]
|
|
});
|
|
|
|
await expect(importSource(textSource('fake.xlsx', 'not a workbook'), options, browserImportRuntimeAdapters)).rejects.toMatchObject({
|
|
diagnostics: [expect.objectContaining({ code: 'excel-signature-mismatch' })]
|
|
});
|
|
});
|
|
|
|
it('applies XML security validation to content even when the extension says text', async () => {
|
|
const source = textSource('synthetic.txt', '<!DOCTYPE report [<!ENTITY secret SYSTEM "file:///secret">]><report>&secret;</report>', 'text/plain');
|
|
await expect(importSource(source, options, browserImportRuntimeAdapters)).rejects.toMatchObject({
|
|
diagnostics: [expect.objectContaining({ code: 'xml-doctype-rejected' })]
|
|
});
|
|
const disguisedJson = textSource('nested.txt', '{"level":{"value":1}}', 'text/plain');
|
|
await expect(importSource(disguisedJson, {
|
|
...options,
|
|
resourcePolicy: { maximumJsonDepth: 1 }
|
|
}, browserImportRuntimeAdapters)).rejects.toMatchObject({
|
|
diagnostics: [expect.objectContaining({ code: 'json-depth-limit' })]
|
|
});
|
|
});
|
|
});
|