mediaDrop/tests/paths.test.ts
Nicolas b85607b096 Dépôt de dossiers + très gros fichiers
Dépôt de dossiers :
- flag `allowDirectories` (défaut false) par cible dans config.json + schéma.
- POST /upload accepte `?path=<chemin relatif>` : l'arborescence est recréée
  sous `dir`, nom d'origine conservé, fichier écrasé si présent.
- src/lib/paths.ts : assainissement segment par segment (pas de `..`, pas de
  séparateur) + vérification de confinement dans storage.ts -> aucune sortie
  possible de la cible. Refus 400 si la cible n'autorise pas les dossiers.
- GET /targets expose `allowDirectories`.
- Page : glisser-déposer d'un dossier (parcours récursif webkitGetAsEntry) +
  bouton « parcourir un dossier », visibles selon la cible.

Très gros fichiers (~10 Go) :
- `requestTimeout: 0` / `connectionTimeout: 0` : aucun timeout serveur imposé.
- écriture disque avec un tampon de 1 Mio.
- doc : `maxFileSizeMb` à l'échelle Go, réglages reverse proxy.

Tests : +paths.test.ts, +placement (storage), +dossier & gros fichier (upload) ;
60 tests.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-10 00:19:20 +02:00

53 lines
1.9 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import { ValidationError } from '../src/errors.js';
import { parseRelativePath } from '../src/lib/paths.js';
describe('parseRelativePath', () => {
it('sépare dossiers et nom de fichier', () => {
expect(parseRelativePath('vacances/plage/img 001.jpg')).toEqual({
segments: ['vacances', 'plage'],
fileName: 'img 001.jpg',
});
});
it('accepte un simple nom de fichier (aucun sous-dossier)', () => {
expect(parseRelativePath('photo.png')).toEqual({ segments: [], fileName: 'photo.png' });
});
it('accepte les séparateurs `/` et `\\` et les segments accentués', () => {
expect(parseRelativePath('Été 2024\\sous-dossier/clé.mp4')).toEqual({
segments: ['Été 2024', 'sous-dossier'],
fileName: 'clé.mp4',
});
});
it('neutralise `.` et `..` (aucune remontée possible)', () => {
expect(parseRelativePath('../../etc/passwd')).toEqual({
segments: ['etc'],
fileName: 'passwd',
});
expect(parseRelativePath('a/./b/../c/d.txt')).toEqual({
segments: ['a', 'b', 'c'],
fileName: 'd.txt',
});
});
it('retire les caractères de contrôle et remplace les caractères hostiles', () => {
const parsed = parseRelativePath(`a${String.fromCharCode(1)}b/fi:le?.txt`);
expect(parsed.segments).toEqual(['ab']);
expect(parsed.fileName).toBe('fi_le_.txt');
});
it('rejette un chemin vide après nettoyage', () => {
expect(() => parseRelativePath('../..')).toThrow(ValidationError);
expect(() => parseRelativePath('///')).toThrow(ValidationError);
});
it('rejette une arborescence trop profonde ou trop longue', () => {
expect(() => parseRelativePath(Array(40).fill('d').join('/') + '/f.txt')).toThrow(
ValidationError,
);
expect(() => parseRelativePath('a/'.repeat(3000) + 'f.txt')).toThrow(ValidationError);
});
});