// 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`);