Files
NewTube/server/tests/odysee-resolve.test.mjs
T
bruno c45e04af42 perf(details): Odysee par l'API LBRY + cache des métadonnées (45 s → 0,2 s)
Mesuré avant : `/api/details/odysee/…` = 44,7 s puis « No video formats
found! » — la page Watch restait une minute sans titre, vues, description ni
vignette. L'extracteur `lbry` de yt-dlp n'est pas utilisable ici.

- Nouvelle branche Odysee dans /api/details : `resolve` de l'API LBRY
  (`server/providers/odysee.mjs` → `resolveOdyseeVideo`) = 229 ms mesurés,
  titre/description/vignette/durée/date/canal + avatar. Claim introuvable →
  404 `odysee_claim_not_found` (le client garde les données du flux), panne →
  502 `odysee_details_failed`. Jamais de repli yt-dlp (45 s pour échouer).
- `views` volontairement OMIS pour Odysee : ni LBRY ni lighthouse n'exposent
  de compteur pour un claim isolé (l'`effective_amount` est un montant LBC).
- Cache mémoire de /api/details : 6 h, LRU 500 (`DETAILS_CACHE_TTL_MS`,
  `DETAILS_CACHE_MAX_ENTRIES`) — métadonnées immuables, chaque /watch
  relançait un dump complet.
- `views`/`duration` ne sont plus émis quand ils valent 0 : un 0 écrasait la
  valeur du flux côté client (merge sur `typeof === 'number'`), d'où les
  « 0 vue » sur les fournisseurs muets — même doctrine que
  `parseRumbleViews` (« jamais de compteur à 0 »).
- Mapping pur `odyseeClaimToVideo` testé hors ligne : 21 assertions,
  `npm run test:odysee`.
2026-10-02 14:48:53 -04:00

71 lines
3.6 KiB
JavaScript
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.
// Assertions offline sur le mapping d'un claim Odysee (API LBRY `resolve`).
// Fixtures calquées sur une VRAIE réponse (probe du 2026-10), pas inventées :
// value.video.duration en secondes, release_time en unix-secondes (chaîne),
// canal dans `signing_channel` (nom + vignette).
import { odyseeClaimToVideo } from '../providers/odysee.mjs';
let pass = 0;
const eq = (a, b, m) => {
const x = JSON.stringify(a), y = JSON.stringify(b);
if (x !== y) throw new Error(`${m}\n expected ${y}\n got ${x}`);
pass++; console.log(` V ${m}`);
};
const claim = (value, channel) => ({
result: {
[`${'kippers-sixth-birthday-and-shorts'}:87e34d33`]: {
value,
...(channel ? { signing_channel: channel } : {}),
},
},
});
const videoValue = {
title: 'Kipper’s Sixth Birthday And Shorts',
description: 'Une description réelle.',
thumbnail: { url: 'https://thumbs.odycdn.com/c2de27138d78b8acea50407bf54b2cd0.webp' },
video: { duration: 261, width: 1080, height: 1920 },
release_time: '1790668800',
channel_name: '@Kipper',
};
const signingChannel = { name: '@Kipper', claim_id: 'abc123', value: { thumbnail: { url: 'https://spee.ch/1/7b3c5b020f0a34a5.jpg' } } };
// --- Cas nominal ---
const ok = odyseeClaimToVideo(claim(videoValue, signingChannel));
eq(ok.title, 'Kipper’s Sixth Birthday And Shorts', 'titre mappé');
eq(ok.description, 'Une description réelle.', 'description mappée');
eq(ok.thumbnail, 'https://thumbs.odycdn.com/c2de27138d78b8acea50407bf54b2cd0.webp', 'vignette (odycdn) mappée');
eq(ok.duration, 261, 'durée = value.video.duration (secondes)');
eq(ok.releaseTime, 1790668800, 'release_time unix (chaîne) -> nombre');
eq(ok.uploaderName, '@Kipper', 'canal = signing_channel.name');
eq(ok.uploaderAvatar, 'https://spee.ch/1/7b3c5b020f0a34a5.jpg', 'avatar = vignette de la chaîne');
eq(new Date(ok.releaseTime * 1000).toISOString(), '2026-09-29T08:00:00.000Z', 'release_time -> ISO (route /api/details)');
eq('views' in ok, false, 'AUCUN compteur de vues (LBRY n\'en expose pas : ne jamais inventer 0)');
// --- Dégradations : tout doit être null ou neutre, jamais une valeur inventée ---
eq(odyseeClaimToVideo({ result: { x: { error: { name: 'NOT_FOUND' } } } }), null, 'claim introuvable -> null (l\'appelant garde les données du flux)');
eq(odyseeClaimToVideo({ result: {} }), null, 'résultat vide -> null');
eq(odyseeClaimToVideo(null), null, 'réponse nulle -> null');
eq(odyseeClaimToVideo(claim({ title: 'Post texte', description: 'x' })), null, 'claim SANS flux vidéo -> null (pas de faux titre de vidéo)');
eq(odyseeClaimToVideo(claim({ ...videoValue, video: undefined })), null, 'video absent -> null');
{
const partial = odyseeClaimToVideo(claim({ ...videoValue, video: {}, release_time: undefined, thumbnail: undefined, channel_name: undefined }));
eq(partial.duration, 0, 'durée inconnue -> 0 (la route l\'omet alors du JSON)');
eq(partial.releaseTime, 0, 'date inconnue -> 0');
eq(partial.thumbnail, '', 'vignette absente -> chaîne vide (jamais undefined)');
eq(partial.uploaderName, '', 'canal absent -> chaîne vide');
}
{
// Repli `value.channel_name` quand `signing_channel` manque (claim ancien).
const noChan = odyseeClaimToVideo(claim(videoValue));
eq(noChan.uploaderName, '@Kipper', 'repli sur value.channel_name sans signing_channel');
eq(noChan.uploaderAvatar, '', 'sans signing_channel, aucun avatar inventé');
}
{
// Repli `value.timestamp` quand `release_time` manque.
const ts = odyseeClaimToVideo(claim({ ...videoValue, release_time: undefined, timestamp: 1700000000 }));
eq(ts.releaseTime, 1700000000, 'repli sur value.timestamp');
}
console.log(`\n odysee-resolve: ${pass} assertions OK`);