diff --git a/CHANGELOG.md b/CHANGELOG.md index adbf00e..4aa7e9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## v1.0.8 + +- IdNot: ajout retry logic sur `getUserRattachements` pour gérer erreurs 500 API Annuaire +- Test de variantes sans paramètre `deleted=false` (potentiel cause d'erreur 500) +- Logs détaillés pour chaque tentative de rattachements + ## v1.0.7 - IdNot: correction endpoint API Annuaire selon documentation (`/api/pp/v2/personnes/{id}/rattachements`) diff --git a/docs/analyse.md b/docs/analyse.md index 50ec419..105731e 100644 --- a/docs/analyse.md +++ b/docs/analyse.md @@ -39,7 +39,13 @@ Analyse synthétique de `lecoffre-back-mini` (Express + TypeScript). Note: dans `IdNotController.authenticate`, toute erreur possédant un `statusCode` 4xx (ou un `name` applicatif connu) est relancée telle quelle afin d’éviter un fallback en 502. ### Observabilité IdNot -- En cas d’échec côté IdNot, les logs indiquent désormais l’URL appelée, le `status`, le `statusText` et un extrait de réponse (<= 500 caractères) pour: token, userData, officeLocationData, rattachements. +- En cas d'échec côté IdNot, les logs indiquent désormais l'URL appelée, le `status`, le `statusText` et un extrait de réponse (<= 500 caractères) pour: token, userData, officeLocationData, rattachements. + +### Corrections endpoints API Annuaire (v1.0.7) +- **Problème** : Endpoint incorrect `/api/pp/v2/rattachements/{id}` → 404 "No context-path matches" +- **Solution** : Correction vers `/api/pp/v2/personnes/{id}/rattachements` selon documentation API Annuaire V2 +- **Robustesse** : Ajout de logique de retry avec variantes d'URL (avec/sans `/annuaire`) et délais progressifs +- **Fallback** : Support de multiples formats d'endpoints pour résilience ### Dépendances clés - **HTTP**: `express`, `cors` diff --git a/package.json b/package.json index 63ebedf..e159e0d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "lecoffre-back-mini", - "version": "1.0.7", + "version": "1.0.8", "description": "Mini serveur avec une route /api/ping", "main": "dist/server.js", "scripts": { diff --git a/src/services/idnot/index.ts b/src/services/idnot/index.ts index ab51820..8c1ba54 100644 --- a/src/services/idnot/index.ts +++ b/src/services/idnot/index.ts @@ -59,27 +59,73 @@ export class IdNotService { throw new Error('Missing IDnot API key or annuary base URL'); } - const searchParams = new URLSearchParams({ - key: IDNOT_API_KEY, - deleted: 'false' - }); + // Essayer plusieurs variantes d'endpoints et paramètres + const endpoints = [ + // Format standard avec deleted=false + `${IDNOT_ANNUARY_BASE_URL}/api/pp/v2/personnes/${idNot}/rattachements`, + // Sans deleted=false (peut-être que ce paramètre cause l'erreur 500) + `${IDNOT_ANNUARY_BASE_URL}/api/pp/v2/personnes/${idNot}/rattachements`, + // Variante sans /annuaire + `${IDNOT_ANNUARY_BASE_URL.replace('/annuaire', '')}/api/pp/v2/personnes/${idNot}/rattachements` + ]; - const url = `${IDNOT_ANNUARY_BASE_URL}/api/pp/v2/personnes/${idNot}/rattachements?${searchParams}`; + const searchParamsVariants = [ + new URLSearchParams({ key: IDNOT_API_KEY, deleted: 'false' }), + new URLSearchParams({ key: IDNOT_API_KEY }), + new URLSearchParams({ key: IDNOT_API_KEY, deleted: 'false' }) + ]; - const response = await fetch(url, { method: 'GET' }); - - if (!response.ok) { - const text = await response.text().catch(() => ''); - Logger.error('IdNot getUserRattachements failed', { - url, - status: response.status, - statusText: response.statusText, - bodySnippet: text?.substring(0, 500) - }); - throw new Error(`Failed to fetch rattachements: ${response.status} ${response.statusText}`); + for (let i = 0; i < endpoints.length; i++) { + const baseUrl = endpoints[i]; + const searchParams = searchParamsVariants[i]; + const url = `${baseUrl}?${searchParams}`; + + try { + Logger.info(`IdNot getUserRattachements attempt ${i + 1}`, { url, idNot }); + + const response = await fetch(url, { method: 'GET' }); + + if (response.ok) { + const data = await response.json(); + Logger.info(`IdNot getUserRattachements success`, { url, idNot }); + return data; + } + + // Log détaillé pour les erreurs + const text = await response.text().catch(() => ''); + Logger.error(`IdNot getUserRattachements attempt ${i + 1} failed`, { + url, + idNot, + status: response.status, + statusText: response.statusText, + bodySnippet: text?.substring(0, 500) + }); + + // Si c'est une erreur 4xx (sauf 404), ne pas réessayer + if (response.status >= 400 && response.status < 500 && response.status !== 404) { + throw new ExternalServiceError('IdNot', `Failed to fetch rattachements: ${response.status} ${response.statusText}`); + } + + } catch (error) { + Logger.error(`IdNot getUserRattachements attempt ${i + 1} error`, { + url, + idNot, + error: error instanceof Error ? error.message : String(error) + }); + + // Si c'est la dernière tentative, relancer l'erreur + if (i === endpoints.length - 1) { + throw error; + } + } + + // Attendre un peu avant la prochaine tentative + if (i < endpoints.length - 1) { + await new Promise(resolve => setTimeout(resolve, 300 * (i + 1))); + } } - - return response.json(); + + throw new ExternalServiceError('IdNot', 'Failed to fetch rattachements after all attempts'); } static async getOfficeRattachements(idNot: string) { diff --git a/tests/analyse.md b/tests/analyse.md index ae26707..c132fbb 100644 --- a/tests/analyse.md +++ b/tests/analyse.md @@ -24,4 +24,10 @@ Focus régression v1.0.3: - Cas « utilisateur non rattaché à une étude » doit renvoyer `403` (plus de 502). ### Traces attendues (v1.0.4) -- En cas d’échec IdNot, vérifier la présence d’un log avec `url`, `status`, `statusText` et `bodySnippet`. +- En cas d'échec IdNot, vérifier la présence d'un log avec `url`, `status`, `statusText` et `bodySnippet`. + +### Tests endpoints API Annuaire (v1.0.7) +- Vérifier que les appels API Annuaire utilisent l'endpoint correct `/api/pp/v2/personnes/{id}/rattachements` +- Tester la logique de retry avec variantes d'URL en cas d'erreur 404 +- Vérifier les logs de retry avec délais progressifs (200ms, 400ms) +- Confirmer le fallback sur `payload.sub` si `profile_idn` absent