youtubeDownloader/server/test/videoSummary.test.ts
Nicolas f0dec86b19
Some checks failed
Publier l'image Docker / test (pull_request) Failing after 4s
Publier l'image Docker / publish (pull_request) Has been skipped
feat: playlists YouTube (parcourir, ajouter une par une ou d'un coup)
Coller un lien de playlist (youtube.com/playlist?list=...) dans le champ
URL affiche toutes ses videos dans la meme zone de resultats que la
recherche de chaine ; chaque ligne a son bouton "Ajouter", et un bouton
"Tout ajouter a la file" ajoute le reste d'un coup.

Serveur :
- GET /api/playlist (PlaylistDiscoveryService) : yt-dlp --flat-playlist
  -J. -J plutot que -j : le titre de la playlist n'existe qu'a la racine,
  et une playlist vide reste un resultat valide.
- playlistUrl.ts : n'accepte que les liens youtube.com/playlist?list=<id>
  et reconstruit l'URL canonique depuis le seul identifiant valide (rien
  d'autre de l'entree n'atteint yt-dlp). Un lien watch?v=...&list=...
  reste une video (--no-playlist etait deja passe au telechargement).
- videoSummary.ts : conversion d'une entree --flat-playlist en resume de
  video, extraite de ChannelDiscoveryService pour etre partagee (chaine
  et playlist parsent desormais identiquement). ChannelVideoSummary
  devient VideoSummary (et ChannelVideoDto -> VideoDto cote client).

Client :
- Le champ URL accepte video ou playlist (isPlaylistUrl aiguille) ; zone
  de resultats partagee (createResultsView).
- "Tout ajouter" : POST /api/queue sequentiels (ordre conserve, pas de
  doublon avec les lignes deja ajoutees). Un echec n'interrompt pas le
  lot : nombre d'echecs + premier message affiches, seules les lignes en
  echec redeviennent ajoutables. Le mode est lu une seule fois pour tout
  le lot.
- Miniatures de resultats en loading="lazy" (une playlist peut compter
  des centaines de videos).

Verifie contre le vrai yt-dlp (2026.07.04) : forme reelle de -J relevee
(fixtures de test calquees dessus), playlist de 1607 videos listee en
~11 s (delai fixe a 120 s). Teste dans le navigateur reel : playlist de
13 videos, ajout unitaire, "tout ajouter" (13 elements, aucun doublon),
echec partiel simule + reprise, playlist inexistante, lien video avec
list=, recherche de chaine (non-regression).

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

70 lines
2.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { describe, expect, it } from 'vitest';
import { summarizeEntry } from '../src/services/videoSummary.js';
const base = { id: 'abc', title: 'Titre', url: 'https://www.youtube.com/watch?v=abc' };
describe('summarizeEntry', () => {
it('extrait les champs connus et met null pour les champs absents', () => {
expect(summarizeEntry(base)).toEqual({
...base,
thumbnailUrl: null,
durationSeconds: null,
uploadDate: null,
});
});
it('lit durée et date de mise en ligne quand elles sont bien typées', () => {
const summary = summarizeEntry({ ...base, duration: 120, upload_date: '20260101' });
expect(summary?.durationSeconds).toBe(120);
expect(summary?.uploadDate).toBe('20260101');
});
it('ignore une durée ou une date de mauvais type', () => {
const summary = summarizeEntry({ ...base, duration: '120', upload_date: 20260101 });
expect(summary?.durationSeconds).toBeNull();
expect(summary?.uploadDate).toBeNull();
});
it('préfère le champ singulier thumbnail au tableau thumbnails', () => {
const summary = summarizeEntry({
...base,
thumbnail: 'https://x/single.jpg',
thumbnails: [{ url: 'https://x/array.jpg' }],
});
expect(summary?.thumbnailUrl).toBe('https://x/single.jpg');
});
it('retombe sur la dernière miniature du tableau thumbnails', () => {
const summary = summarizeEntry({
...base,
thumbnails: [{ url: 'https://x/small.jpg' }, { url: 'https://x/large.jpg' }],
});
expect(summary?.thumbnailUrl).toBe('https://x/large.jpg');
});
it('ignore un tableau thumbnails vide ou mal formé', () => {
expect(summarizeEntry({ ...base, thumbnails: [] })?.thumbnailUrl).toBeNull();
expect(summarizeEntry({ ...base, thumbnails: [{ width: 10 }] })?.thumbnailUrl).toBeNull();
expect(summarizeEntry({ ...base, thumbnails: [null] })?.thumbnailUrl).toBeNull();
});
it.each([
['sans id', { title: 'T', url: 'https://x' }],
['sans titre', { id: 'a', url: 'https://x' }],
['sans url', { id: 'a', title: 'T' }],
['avec un id non textuel', { id: 42, title: 'T', url: 'https://x' }],
])('retourne null pour une entrée %s', (_label, entry) => {
expect(summarizeEntry(entry)).toBeNull();
});
it.each([null, undefined, 'texte', 42, [], [base]])(
'retourne null pour une valeur qui n’est pas un objet (%j)',
(value) => {
expect(summarizeEntry(value)).toBeNull();
},
);
});