feat(providers): phases 7.3/7.4/7.6/8.1 — provenance, health, NDJSON, contrat unique
CI / build-and-test (push) Successful in 14m43s
CI / build-and-test (push) Successful in 14m43s
7.3: capturedAt/source au registre + 6 adaptateurs + module provenance.ts + ?debug=1 (search-transport.mjs). 7.4: ProviderHealthService + badge source degradee. 7.6: squelettes par provider + snapshots progressifs + transport NDJSON /api/search. 8.1: ProviderAdapter unifie (search enveloppe + channelContent/channelMeta/capabilities) via getProviderAdapter + test de contrat offline.
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* 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);
|
||||
Reference in New Issue
Block a user