mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-19 21:31:46 +00:00
801ffbfb7b
- Strip BOM (U+FEFF) from 14 translation files injected by editor - Guard KMZ unpack against zip-bomb: check entry.uncompressedSize against 50 MB cap (KMZ_DECOMPRESSED_SIZE_LIMIT) before calling .buffer(); limit is an exported constant so tests can override it - Fix non-BMP HTML entity decoding: replace String.fromCharCode with String.fromCodePoint + 0x10FFFF bounds check so emoji like 😀 round-trip correctly - Switch KML namespace stripping from regex to fast-xml-parser's removeNSPrefix option; XMLValidator accepts namespaced XML natively, making the pre-strip step unnecessary - Remove dead skippedCount overwrite after transaction; per-loop increment already tracks it alongside per-item error messages - Type multer req.file as Express.Multer.File on both /import/gpx and /import/map routes instead of (req as any).file - Add unit tests: emoji entity decoding (decimal + hex), KMZ zip-bomb rejection, KMZ-with-no-KML rejection
80 lines
2.5 KiB
TypeScript
80 lines
2.5 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import {
|
|
buildCategoryNameLookup,
|
|
decodeUtf8WithWarning,
|
|
extractKmlPlacemarkNodes,
|
|
parseKmlPointCoordinates,
|
|
parsePlacemarkNode,
|
|
resolveCategoryIdForFolder,
|
|
sanitizeKmlDescription,
|
|
} from '../../../src/services/kmlImport';
|
|
|
|
describe('kmlImportUtils', () => {
|
|
it('sanitizes HTML descriptions with br to newline', () => {
|
|
const input = 'Line 1<br>Line <b>2</b> & more';
|
|
const output = sanitizeKmlDescription(input);
|
|
expect(output).toBe('Line 1\nLine 2 & more');
|
|
});
|
|
|
|
it('parses KML coordinate order lng,lat,alt', () => {
|
|
const parsed = parseKmlPointCoordinates('13.4050,52.5200,15');
|
|
expect(parsed).toEqual({ lat: 52.52, lng: 13.405 });
|
|
});
|
|
|
|
it('extracts placemarks from nested folders', () => {
|
|
const root = {
|
|
Document: {
|
|
Folder: {
|
|
name: 'Parent',
|
|
Folder: {
|
|
name: 'Child',
|
|
Placemark: { name: 'Nested', Point: { coordinates: '13.4,52.5,0' } },
|
|
},
|
|
},
|
|
},
|
|
};
|
|
|
|
const nodes = extractKmlPlacemarkNodes(root);
|
|
expect(nodes).toHaveLength(1);
|
|
expect(nodes[0].folderName).toBe('Child');
|
|
|
|
const parsed = parsePlacemarkNode(nodes[0]);
|
|
expect(parsed.name).toBe('Nested');
|
|
expect(parsed.lat).toBe(52.5);
|
|
expect(parsed.lng).toBe(13.4);
|
|
});
|
|
|
|
it('builds exact case-insensitive category lookup', () => {
|
|
const lookup = buildCategoryNameLookup([
|
|
{ id: 3, name: 'Museums' },
|
|
{ id: 4, name: 'Parks' },
|
|
]);
|
|
|
|
expect(resolveCategoryIdForFolder('museums', lookup)).toBe(3);
|
|
expect(resolveCategoryIdForFolder('Museum', lookup)).toBeNull();
|
|
expect(resolveCategoryIdForFolder('parks', lookup)).toBe(4);
|
|
});
|
|
|
|
it('decodes non-BMP decimal HTML entities (emoji)', () => {
|
|
// 😀 = U+1F600 = 😀 — requires String.fromCodePoint, not fromCharCode
|
|
expect(sanitizeKmlDescription('😀')).toBe('😀');
|
|
});
|
|
|
|
it('decodes non-BMP hex HTML entities (emoji)', () => {
|
|
// 😀 = U+1F600 = 😀
|
|
expect(sanitizeKmlDescription('😀')).toBe('😀');
|
|
});
|
|
|
|
it('returns warning for non-UTF8 payload', () => {
|
|
const buffer = Buffer.concat([
|
|
Buffer.from('<?xml version="1.0"?><kml><Document><Placemark><name>Caf'),
|
|
Buffer.from([0xe9]),
|
|
Buffer.from('</name></Placemark></Document></kml>'),
|
|
]);
|
|
|
|
const decoded = decodeUtf8WithWarning(buffer);
|
|
expect(decoded.warning).toContain('not valid UTF-8');
|
|
expect(decoded.text).toContain('<kml>');
|
|
});
|
|
});
|