feat(couverture): 100pc API/MCP/tests/docs - volet 2
CI / build-and-test (push) Successful in 14m6s

- tests: server/tests/api_coverage2.test.mjs (23 cas offline-safe) :
  oauth (status/url/connections/preview), channels (validations),
  subscriptions batch + groupes CRUD, transcripts history, likes/history
  DELETE, playlists public/view/reorder, telemetry summary, downloads
  401/403/400, ai 503, twitch-token, config.js, rumble 400
- mcp v1.1.0 : 4 outils authentifies (list_playlists/likes/
  subscriptions/watch_history, NEWTUBE_TOKEN) + tests stub
- openapi.json v1.1 : 15 chemins documentes (sante, recherche,
  details, trending, transcript, auth, prefs, likes, subs, downloads,
  oauth, playlists) ; guide maj (11 outils)
This commit is contained in:
2026-09-27 18:51:50 -04:00
parent 1812b86dd4
commit 148a00e60f
7 changed files with 405 additions and 8 deletions
+71 -2
View File
@@ -3779,12 +3779,81 @@ r.put('/playlists/:id/reorder', authMiddlewareCookieAware, (req, res) => {
}
});
// --- OpenAPI (minimal for playlists) ---
// --- OpenAPI (référence machine : lecture publique + espace utilisateur) ---
r.get('/openapi.json', (_req, res) => {
const bearer = [{ bearerAuth: [] }];
const doc = {
openapi: '3.0.0',
info: { title: 'NewTube API', version: '1.0.0' },
info: { title: 'NewTube API', version: '1.1.0' },
paths: {
'/healthz': {
get: { summary: 'Santé API (mode YT, yt-dlp, cache, quota)', responses: { 200: { description: 'ok' } } },
},
'/search': {
get: { summary: 'Recherche unifiée multi-providers', parameters: [
{ name: 'q', in: 'query', required: true, schema: { type: 'string', minLength: 2 } },
{ name: 'providers', in: 'query', schema: { type: 'string', example: 'yt,dm' } },
{ name: 'page', in: 'query', schema: { type: 'integer', default: 1 } },
{ name: 'pageSize', in: 'query', schema: { type: 'integer', maximum: 50 } },
{ name: 'sort', in: 'query', schema: { type: 'string', enum: ['relevance', 'date', 'views'] } },
], responses: { 200: { description: '{ q, providers, groups, errors, page, pageSize, sort }' } } },
},
'/search/suggest': {
get: { summary: 'Typeahead groupé par provider', parameters: [
{ name: 'q', in: 'query', required: true, schema: { type: 'string', minLength: 2 } },
{ name: 'providers', in: 'query', schema: { type: 'string' } },
{ name: 'limit', in: 'query', schema: { type: 'integer', maximum: 20 } },
], responses: { 200: { description: '{ q, groups }' } } },
},
'/details/{provider}/{videoId}': {
get: { summary: 'Métadonnées vidéo + connexes YouTube', parameters: [
{ name: 'provider', in: 'path', required: true, schema: { type: 'string' } },
{ name: 'videoId', in: 'path', required: true, schema: { type: 'string' } },
{ name: 'instance', in: 'query', schema: { type: 'string' } },
{ name: 'related', in: 'query', schema: { type: 'string', enum: ['0'] } },
], responses: { 200: { description: 'video + related[]' } } },
},
'/trending': {
get: { summary: 'Tendances YouTube sans clé (phase 1 : yt)', parameters: [
{ name: 'provider', in: 'query', schema: { type: 'string', default: 'yt' } },
{ name: 'limit', in: 'query', schema: { type: 'integer', maximum: 50 } },
], responses: { 200: { description: '{ provider, items }' } } },
},
'/transcript/{provider}/{videoId}': {
get: { summary: 'Transcript { lang, available, languages, lines }', parameters: [
{ name: 'provider', in: 'path', required: true, schema: { type: 'string' } },
{ name: 'videoId', in: 'path', required: true, schema: { type: 'string' } },
{ name: 'lang', in: 'query', schema: { type: 'string', default: 'fr' } },
{ name: 'instance', in: 'query', schema: { type: 'string' } },
], responses: { 200: { description: '200 available:true|false ; 502 retryable:true si 429 YouTube' } } },
},
'/auth/register': {
post: { summary: 'Créer un compte', responses: { 201: { description: '{ user, accessToken }' } } },
},
'/auth/login': {
post: { summary: 'Connexion (retourne accessToken + cookies)', responses: { 200: { description: '{ user, accessToken }' } } },
},
'/user/me': {
get: { summary: 'Profil courant', security: bearer, responses: { 200: { description: 'user' } } },
},
'/user/preferences': {
get: { summary: 'Lire préférences', security: bearer, responses: { 200: { description: 'prefs' } } },
patch: { summary: 'Modifier préférences (defaultProviders…)', security: bearer, responses: { 200: { description: 'prefs' } } },
},
'/user/likes': {
get: { summary: 'Vidéos likées', security: bearer, responses: { 200: { description: 'likes[]' } } },
post: { summary: 'Liker', security: bearer, responses: { 201: { description: 'like' } } },
},
'/subscriptions': {
get: { summary: 'Abonnements { items, ttl }', security: bearer, responses: { 200: { description: 'subs' } } },
post: { summary: 'S abonner (résolution chaîne)', security: bearer, responses: { 201: { description: 'sub' } } },
},
'/download/{provider}/{videoId}/formats': {
get: { summary: 'Formats téléchargeables (cache 10 min)', security: bearer, responses: { 200: { description: '{ url, formats }' } } },
},
'/oauth/status': {
get: { summary: 'Connecteurs OAuth configurés', responses: { 200: { description: '{ google, twitch }' } } },
},
'/playlists': {
get: { summary: 'List user playlists', security: [{ bearerAuth: [] }], parameters: [
{ name: 'limit', in: 'query', schema: { type: 'integer' } },