/** * Phase 8.4 — enregistreur de fixtures de contrat. * * Appelle les 6 adaptateurs **réels** et fige leur sortie dans * `server/tests/fixtures/provider-suggestions.json`. Les fixtures sont donc * générées, pas écrites à la main : elles reflètent ce que les providers * produisent vraiment, y compris leurs champs bizarres. * * Réflexe : figer des objets inventés ne prouverait rien — on validerait sa * propre imagination. Figer la sortie réelle prouve que le code d'aujourd'hui * respecte le contrat, et le test rejoué à chaque PR détecte toute dérive. * * Usage : `npm run fixtures:record` (réseau requis). * En CI : jamais exécuté. On vérifie seulement que les fixtures gelées sont * toujours valides (`npm run test:shapes`). */ import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; const OUT = path.resolve(import.meta.dirname, 'fixtures/provider-suggestions.json'); const QUERY = process.argv[2] || 'tutorial'; const LIMIT = 3; // BASE DE DONNÉES JETABLE, définie AVANT tout import du registre. // // Deux raisons, toutes deux apprises à l'usage : // 1. `search_cache` est persistant : sans base neuve, le gel rejoue des // résultats d'un ancien gel et fige une forme PÉRIMÉE. C'est arrivé : le // correctif de `language` PeerTube n'apparaissait pas dans les fixtures. // 2. Le gel ne doit pas toucher `db/newtube.db` (ni y appliquer de migration). const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'newtube-record-')); process.env.NEWTUBE_DB_FILE = path.join(tmpDir, 'record.db'); const { providerRegistry } = await import('../providers/registry.mjs'); /** Champs parasites à ne pas figer : ils changent à chaque appel et ne disent rien du contrat. */ function normalize(item) { const out = {}; // Tri des clés : deux enregistrements successifs donnent un diff lisible. for (const k of Object.keys(item).sort()) { if (item[k] === undefined) continue; out[k] = item[k]; } return out; } const recorded = { _comment: 'FIGÉ PAR `npm run fixtures:record`. Ne pas éditer à la main : régénérer.', _contractVersion: 2, _recordedAt: new Date().toISOString(), _query: QUERY, providers: {}, }; let failures = 0; for (const id of Object.keys(providerRegistry)) { process.stdout.write(` ${id} … `); try { const items = await providerRegistry[id].search(QUERY, { limit: LIMIT, page: 1, sort: 'relevance' }); const list = (Array.isArray(items) ? items : []).map(normalize); recorded.providers[id] = list.length ? { items: list } // Upstream indisponible au moment du gel (Rumble/Cloudflare, Odysee hors // ligne) : on le note au lieu d'écrire un tableau vide silencieux, pour // que la couverture manquante reste visible dans le fichier. : { items: [], note: 'aucun résultat au moment du gel — couverture non testée pour ce provider' }; console.log(`${list.length} item(s)${list.length ? '' : ' ⚠'}`); if (!list.length) failures++; } catch (e) { recorded.providers[id] = { items: [], note: `erreur au gel : ${String(e?.message || e).slice(0, 160)}` }; console.log(`ERREUR ${e?.message || e}`); failures++; } } fs.mkdirSync(path.dirname(OUT), { recursive: true }); fs.writeFileSync(OUT, `${JSON.stringify(recorded, null, 2)}\n`); console.log(`\n→ ${path.relative(process.cwd(), OUT)}`); if (failures) { console.log(`\n${failures} provider(s) sans fixture exploitable : la couverture du test de contrat est partielle.`); } try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch {} // Exit 1 : le gel a réussi mais est partiel (upstream HS). Le fichier est // quand même écrit, pour que le trou de couverture soit visible dans le gel. process.exit(failures ? 1 : 0);