feat(referentiels): ajout d'un test e2e pour le démarrage de l'audit,…#4258
feat(referentiels): ajout d'un test e2e pour le démarrage de l'audit,…#4258
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughRemplacement des requêtes serveur pour audits par dérivations côté client depuis Changes
Sequence Diagram(s)sequenceDiagram
participant Client as Frontend (useRapportsAudit)
participant TRPC as TRPC Router (listPreuvesAudit)
participant Service as ListPreuvesService
participant DB as Database
Client->>TRPC: listPreuvesAudit(auditId)
TRPC->>Service: validate input & user, call listPreuvesAudit({auditId}, user)
Service->>DB: SELECT ... JOIN fichier/audit/demande WHERE audit.id = auditId
DB-->>Service: rows (preuves with fichier metadata)
Service-->>TRPC: Result success (preuves[])
TRPC-->>Client: preuves[]
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches🧪 Generate unit tests (beta)
Tip Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In `@apps/app/src/referentiels/audits/useAudit.ts`:
- Around line 48-54: The hook useIsAuditAuditeur should explicitly guard against
an undefined audit_id to make the intent clear: at the start of
useIsAuditAuditeur, check if audit_id is undefined and immediately return false;
then proceed to use user.collectivites.some(...) as before to check
audit.auditId === audit_id. This makes the early-exit explicit and improves
readability while keeping the existing logic in the function.
In `@apps/backend/src/referentiels/labellisations/labellisations.test-fixture.ts`:
- Around line 116-149: In requestLabellisationForCot, fix the confusing comment
and simplify the object shorthand: change the comment "// No need to upload file
for not cot" to the intended text (e.g., "// No need to upload file for cot")
near the getParcours call to reflect COT context, and when calling
trpcClient.referentiels.labellisations.requestLabellisation.mutate use ES6
property shorthand for collectiviteId (i.e., pass collectiviteId instead of
collectiviteId: collectiviteId) while keeping referentiel, sujet and etoiles
as-is so the payload is concise and correct.
In `@e2e/tests/referentiels/labellisations/start-labellisation-cot.spec.ts`:
- Line 35: The test title string passed to the test(...) call contains a typo
("l'auiteur"); update the test name in the test("Possibilité de démarrer un
audit pour l'auditeur, une fois que la demande de labellisation est soumise et
l'auiteur attribué", ...) to correct the spelling to "l'auditeur" (e.g., change
"l'auiteur attribué" to "l'auditeur attribué") so the test description reads
correctly.
🧹 Nitpick comments (4)
e2e/tests/referentiels/labellisations/start-labellisation-cot.spec.ts (2)
47-54: L'accès par indexgetUser(1)est fragile.
collectivite.getUser(1)repose sur l'ordre d'insertion des utilisateurs. Si un autre utilisateur est ajouté dans lebeforeEachou si l'ordre change, le test cassera silencieusement. Envisagez de stockerauditeurUserdans le contexte du test (via une variable partagée ou un fixture dédié) plutôt que de dépendre d'un index numérique.
9-33: Le setup dansbeforeEachcrée l'état mais les variables ne sont pas accessibles dans le test.Les variables
editeurUser,auditeurUseretcollectivitesont créées dansbeforeEachmais récupérées dans le test viacollectivites.getCollectivite()etgetUser(index). Cela fonctionne mais c'est peu lisible et fragile. Un pattern plus robuste serait d'utiliser untest.extendcustom ou de stocker ces références dans un objet partagé accessible depuis le test.Ce n'est pas bloquant dans l'immédiat, c'est une suggestion d'amélioration.
apps/app/src/referentiels/audits/useAudit.ts (1)
37-45: Appel dupliqué deuseLabellisationParcoursentreuseAuditetuseAuditeurs.Les deux hooks
useAudit(ligne 19) etuseAuditeurs(ligne 40) appellentuseLabellisationParcoursavec les mêmes paramètres. Si ces hooks sont utilisés dans le même composant ou arbre de composants, les requêtes seront dédupliquées par React Query grâce au cache. Cependant, si vous cherchez à réduire la duplication, un hook combiné (ex.useAuditData) pourrait centraliser l'appel et exposer les deux valeurs.apps/app/src/referentiels/labellisations/HeaderLabellisation.tsx (1)
169-169: Type inline pourauditeurs— envisager un type nommé partagé.Le type
{ userId: string; nom: string }[]est défini inline ici. Si ce même type est utilisé ou susceptible d'être réutilisé ailleurs (par exemple dansuseAuditeurs), un type nommé partagé améliorerait la cohérence et la maintenabilité.
| export const useIsAuditAuditeur = (audit_id?: number) => { | ||
| const user = useUser(); | ||
| const { data: auditeurs } = useAuditAuditeurs(audit_id); | ||
| if (!user || !auditeurs?.length) { | ||
| return false; | ||
| } | ||
| return auditeurs.findIndex(({ auditeur }) => auditeur === user.id) !== -1; | ||
| }; | ||
|
|
||
| export type TAuditeur = { nom: string; prenom: string }; | ||
| const fetchAuditeurs = async ( | ||
| supabase: DBClient, | ||
| collectivite_id: number, | ||
| referentiel: ReferentielId | ||
| ) => { | ||
| const { data, error } = await supabase | ||
| .from('auditeurs') | ||
| .select('noms') | ||
| .match({ collectivite_id, referentiel }) | ||
| .limit(1); | ||
|
|
||
| if (error || !data?.length) { | ||
| return null; | ||
| } | ||
|
|
||
| return data[0].noms as TAuditeur[]; | ||
| return user.collectivites.some((collectivite) => | ||
| collectivite.audits.some((audit) => audit.auditId === audit_id) | ||
| ); | ||
| }; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Search for User type definition to understand the structure of collectivites
fd -t ts -n 'user' -i | head -20Repository: incubateur-ademe/territoires-en-transitions
Length of output: 317
🏁 Script executed:
# Look for type definitions related to User, collectivites, and audits
rg -n --type=ts 'type.*User|interface.*User' -A 10 | head -50Repository: incubateur-ademe/territoires-en-transitions
Length of output: 3334
🏁 Script executed:
# Search for collectivites definition in the codebase
rg -n 'collectivites.*:' --type=ts | head -20Repository: incubateur-ademe/territoires-en-transitions
Length of output: 2008
🏁 Script executed:
# Check how audits is structured in the code
rg -n 'audits.*:.*\[' --type=ts | head -20Repository: incubateur-ademe/territoires-en-transitions
Length of output: 1197
🏁 Script executed:
# Read the UserInfo type definition
cat -n packages/domain/src/users/user-info.schema.ts | head -50Repository: incubateur-ademe/territoires-en-transitions
Length of output: 732
🏁 Script executed:
# Search for Collectivite type definition
rg -n --type=ts "type Collectivite|interface Collectivite" -A 15 | head -80Repository: incubateur-ademe/territoires-en-transitions
Length of output: 7374
🏁 Script executed:
# Look at one of the test files showing the structure to understand collectivites.audits
cat -n apps/app/src/plans/fiches/list-all-fiches/hooks/use-select-fiches.test.ts | head -30Repository: incubateur-ademe/territoires-en-transitions
Length of output: 1208
🏁 Script executed:
# Search for where useUser is defined to understand what it returns
rg -n "export.*useUser|function useUser" --type=ts -A 5 | head -50Repository: incubateur-ademe/territoires-en-transitions
Length of output: 1914
🏁 Script executed:
# Check the full useUser implementation and what user type it returns
cat -n packages/api/src/users/user-context/user-provider.tsx | head -50Repository: incubateur-ademe/territoires-en-transitions
Length of output: 1589
🏁 Script executed:
# Look for where user is defined/set in the context
rg -n "const user|user\.collectivites" packages/api/src/users/user-context/user-provider.tsx -A 3 -B 3 | head -100Repository: incubateur-ademe/territoires-en-transitions
Length of output: 69
🏁 Script executed:
# Find the UserContextType or User type definition used in user-provider
rg -n "type.*User|interface.*User" packages/api/src/users/user-context/user-provider.tsx -B 5 -A 10Repository: incubateur-ademe/territoires-en-transitions
Length of output: 550
🏁 Script executed:
# Search specifically for where collectivites and audits are populated in the user object
rg -n "collectivites.*audits|audits.*auditId" --type=ts -B 2 -A 2 | head -100Repository: incubateur-ademe/territoires-en-transitions
Length of output: 1807
🏁 Script executed:
# Find the UserWithRolesAndPermissions type definition
rg -n "type UserWithRolesAndPermissions|interface UserWithRolesAndPermissions" --type=ts -A 20Repository: incubateur-ademe/territoires-en-transitions
Length of output: 396
🏁 Script executed:
# Look for CollectiviteRolesAndPermissions schema which seems to be the collectivite structure
cat -n packages/domain/src/users/authorizations/user-roles-and-permissions.schema.tsRepository: incubateur-ademe/territoires-en-transitions
Length of output: 1732
🏁 Script executed:
# Search for audit-related types in the domain
fd -t ts audit | grep -i schemaRepository: incubateur-ademe/territoires-en-transitions
Length of output: 256
🏁 Script executed:
# Look at the full UserWithRolesAndPermissions schema definition
cat -n packages/domain/src/users/user-with-roles-and-permissions.schema.tsRepository: incubateur-ademe/territoires-en-transitions
Length of output: 576
🏁 Script executed:
# Check the context and usage of useIsAuditAuditeur to understand the expected behavior
cat -n apps/app/src/referentiels/audits/useAudit.tsRepository: incubateur-ademe/territoires-en-transitions
Length of output: 3646
🏁 Script executed:
# Search for where audit_id could be undefined in the codebase
rg -n "useIsAuditAuditeur" --type=ts -B 2 -A 2 | head -50Repository: incubateur-ademe/territoires-en-transitions
Length of output: 1820
🏁 Script executed:
# Verify the exact type definition of auditId to confirm it's always a number (never undefined)
rg -n "auditId.*z\." --type=ts packages/domain/src/users/authorizations/user-roles-and-permissions.schema.tsRepository: incubateur-ademe/territoires-en-transitions
Length of output: 112
🏁 Script executed:
# Check if there are any tests for useIsAuditAuditeur to understand the expected behavior
rg -n "useIsAuditAuditeur" --type=ts -lRepository: incubateur-ademe/territoires-en-transitions
Length of output: 208
La structure de données est validée, mais la clarté du traitement de audit_id optionnel peut être améliorée.
Les données de user.collectivites[].audits[] avec la propriété auditId sont correctement structurées selon le schéma. Cependant, lorsque audit_id est undefined, la comparaison audit.auditId === undefined retournera toujours false (puisque auditId est toujours un nombre). Le hook retourne donc false, ce qui est techniquement correct, mais un guard explicite au début rendrait l'intention plus lisible.
🤖 Prompt for AI Agents
In `@apps/app/src/referentiels/audits/useAudit.ts` around lines 48 - 54, The hook
useIsAuditAuditeur should explicitly guard against an undefined audit_id to make
the intent clear: at the start of useIsAuditAuditeur, check if audit_id is
undefined and immediately return false; then proceed to use
user.collectivites.some(...) as before to check audit.auditId === audit_id. This
makes the early-exit explicit and improves readability while keeping the
existing logic in the function.
| export async function requestLabellisationForCot( | ||
| trpcClient: TRPCClient<AppRouter>, | ||
| collectiviteId: number, | ||
| referentiel: ReferentielId | ||
| ): Promise<void> { | ||
| // Fill referentiel | ||
| await updateAllNeedReferentielStatutsToCompleteReferentiel( | ||
| trpcClient, | ||
| collectiviteId, | ||
| referentiel | ||
| ); | ||
|
|
||
| // Match score criteria | ||
| await updateAllNeedReferentielStatutsToMatchReferentielScoreCriteria( | ||
| trpcClient, | ||
| collectiviteId, | ||
| referentiel | ||
| ); | ||
|
|
||
| // No need to upload file for not cot | ||
| const parcours = | ||
| await trpcClient.referentiels.labellisations.getParcours.query({ | ||
| collectiviteId: collectiviteId, | ||
| referentielId: referentiel, | ||
| }); | ||
|
|
||
| // Request audit | ||
| await trpcClient.referentiels.labellisations.requestLabellisation.mutate({ | ||
| referentiel, | ||
| collectiviteId, | ||
| sujet: 'labellisation', | ||
| etoiles: parcours.etoiles, | ||
| }); | ||
| } |
There was a problem hiding this comment.
Le commentaire ligne 135 est confus et semble incorrect.
Le commentaire // No need to upload file for not cot est ambigu. S'il s'agit d'un contexte COT (comme le nom de la fonction l'indique), le commentaire devrait probablement être // No need to upload file for cot (sans "not").
De plus, collectiviteId: collectiviteId (ligne 138) peut être simplifié en utilisant le shorthand ES6.
♻️ Corrections proposées
- // No need to upload file for not cot
+ // No need to upload file for cot
const parcours =
await trpcClient.referentiels.labellisations.getParcours.query({
- collectiviteId: collectiviteId,
+ collectiviteId,
referentielId: referentiel,
});🤖 Prompt for AI Agents
In `@apps/backend/src/referentiels/labellisations/labellisations.test-fixture.ts`
around lines 116 - 149, In requestLabellisationForCot, fix the confusing comment
and simplify the object shorthand: change the comment "// No need to upload file
for not cot" to the intended text (e.g., "// No need to upload file for cot")
near the getParcours call to reflect COT context, and when calling
trpcClient.referentiels.labellisations.requestLabellisation.mutate use ES6
property shorthand for collectiviteId (i.e., pass collectiviteId instead of
collectiviteId: collectiviteId) while keeping referentiel, sujet and etoiles
as-is so the payload is concise and correct.
| await page.goto('/'); | ||
| }); | ||
|
|
||
| test("Possibilité de démarrer un audit pour l'auditeur, une fois que la demande de labellisation est soumise et l'auiteur attribué", async ({ |
There was a problem hiding this comment.
Faute de frappe dans le nom du test : "l'auiteur" → "l'auditeur".
- test("Possibilité de démarrer un audit pour l'auditeur, une fois que la demande de labellisation est soumise et l'auiteur attribué", async ({
+ test("Possibilité de démarrer un audit pour l'auditeur, une fois que la demande de labellisation est soumise et l'auditeur attribué", async ({📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| test("Possibilité de démarrer un audit pour l'auditeur, une fois que la demande de labellisation est soumise et l'auiteur attribué", async ({ | |
| test("Possibilité de démarrer un audit pour l'auditeur, une fois que la demande de labellisation est soumise et l'auditeur attribué", async ({ |
🤖 Prompt for AI Agents
In `@e2e/tests/referentiels/labellisations/start-labellisation-cot.spec.ts` at
line 35, The test title string passed to the test(...) call contains a typo
("l'auiteur"); update the test name in the test("Possibilité de démarrer un
audit pour l'auditeur, une fois que la demande de labellisation est soumise et
l'auiteur attribué", ...) to correct the spelling to "l'auditeur" (e.g., change
"l'auiteur attribué" to "l'auditeur attribué") so the test description reads
correctly.
edf024a to
c265a8f
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@e2e/tests/referentiels/labellisations/labellisation.pom.ts`:
- Around line 93-98: The test helper checkAuditEnCoursWithAuditeur assumes the
header shows "prenom nom" but HeaderLabellisation.tsx actually renders
auditeur.nom from parcours; update the test to assert the actual displayed
value: derive the expected display string from the fixture field that matches
HeaderLabellisation (use auditeurUser.data.nom or a helper that maps to
parcours.auditeurs[].nom) or relax the assertion to check for either prenom or
nom separately; modify checkAuditEnCoursWithAuditeur to compare against that
single-field value (reference: checkAuditEnCoursWithAuditeur and
HeaderLabellisation.tsx auditeur.nom).
🧹 Nitpick comments (2)
apps/app/src/referentiels/audits/useAudit.ts (1)
13-25: Bon refactoring — dérivation côté client cohérente avec le statut.La logique
parcours?.status === 'audit_en_cours' ? parcours.audit : nullest correcte : le consumeruseShowDescIntoInfoPanelvérifie déjàaudit.date_debut && !audit.valide, ce qui est redondant avec le filtre sur le statut mais reste safe.Cependant,
useAuditetuseAuditeursappellent chacunuseLabellisationParcoursavec les mêmes paramètres. DansHeaderLabellisation, les deux hooks sont utilisés ensemble, ce qui génère deux appels identiques. Si React Query (ou le cache sous-jacent) déduplique correctement, ce n'est pas un problème fonctionnel, mais un hook combiné serait plus propre.♻️ Proposition : hook combiné pour éviter la double invocation
+export const useAuditAndAuditeurs = () => { + const collectivite_id = useCollectiviteId(); + const referentiel = useReferentielId(); + const parcours = useLabellisationParcours({ + collectiviteId: collectivite_id, + referentielId: referentiel, + }); + const auditEnCours = + parcours?.status === 'audit_en_cours' ? parcours.audit : null; + return { audit: auditEnCours, auditeurs: parcours?.auditeurs }; +};apps/backend/src/collectivites/collectivites/collectivites.test-fixture.ts (1)
45-45: Utilisation incohérente de la variable destructuréeisCOT.Ligne 45,
isCOTest destructuré depuiscollectiviteArgs, mais ligne 65 utilisecollectiviteArgs.isCOTau lieu de la variable localeisCOT. Ce n'est pas un bug, mais par cohérence, préférez utiliser la variable déjà destructurée.♻️ Correction suggérée
- if (collectiviteArgs.isCOT) { + if (isCOT) { await setCollectiviteAsCOT(databaseService, result.id, true); }Also applies to: 65-66
| async checkAuditEnCoursWithAuditeur(auditeurUser: UserFixture) { | ||
| await expect(this.auditEnCoursMessage).toBeVisible(); | ||
| await expect(this.auditEnCoursMessage).toContainText( | ||
| `${auditeurUser.data.prenom} ${auditeurUser.data.nom}` | ||
| ); | ||
| } |
There was a problem hiding this comment.
Risque de discordance entre le nom affiché et le nom vérifié dans le test.
checkAuditEnCoursWithAuditeur vérifie la présence de `${auditeurUser.data.prenom} ${auditeurUser.data.nom}` dans le message. Or, dans HeaderLabellisation.tsx (ligne 177), le header affiche auditeur.nom issu du parcours. Si parcours.auditeurs[].nom ne contient pas exactement "prénom nom", ce test échouera systématiquement.
🤖 Prompt for AI Agents
In `@e2e/tests/referentiels/labellisations/labellisation.pom.ts` around lines 93 -
98, The test helper checkAuditEnCoursWithAuditeur assumes the header shows
"prenom nom" but HeaderLabellisation.tsx actually renders auditeur.nom from
parcours; update the test to assert the actual displayed value: derive the
expected display string from the fixture field that matches HeaderLabellisation
(use auditeurUser.data.nom or a helper that maps to parcours.auditeurs[].nom) or
relax the assertion to check for either prenom or nom separately; modify
checkAuditEnCoursWithAuditeur to compare against that single-field value
(reference: checkAuditEnCoursWithAuditeur and HeaderLabellisation.tsx
auditeur.nom).
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Fix all issues with AI agents
In `@apps/app/src/referentiels/audits/useAudit.ts`:
- Around line 57-71: The hook useRapportsAudit currently masks async state and
uses an unsafe double-cast on preuvesAudit; change it to return the full
useQuery result (data, isLoading, error) instead of a plain TPreuveAudit[] so
callers can handle loading/errors, remove the "as unknown as TPreuveAudit[]"
cast, and pass auditId to
trpc.referentiels.labellisations.listPreuvesAudit.queryOptions only when defined
(don’t use auditId ?? 0) while keeping the query disabled when auditId is falsy;
ensure the returned type matches the trpc query's inferred type from useTRPC so
no manual casting is needed.
In
`@apps/backend/src/referentiels/labellisations/list-preuves/list-preuves.router.e2e-spec.ts`:
- Around line 56-68: Add a clear precondition check for the created audit's
demande before using demande!.id: after calling createAuditWithOnTestFinished
(in this test using withDemande: true) assert that demande is defined (e.g.,
expect(demande).toBeDefined() or throw a descriptive error) and only then pass
demande.id into caller.referentiels.labellisations.listPreuvesLabellisation;
reference createAuditWithOnTestFinished, the local variable demande, and the
call to listPreuvesLabellisation/router.createCaller to locate where to add the
assertion so a setup failure surfaces with a clear message rather than a
null-pointer exception.
In
`@apps/backend/src/referentiels/labellisations/list-preuves/list-preuves.service.ts`:
- Around line 101-119: The innerJoin on collectiviteBucketTable used in the
query starting from preuveAuditTable (the join chain that includes
bibliothequeFichierTable and then collectiviteBucketTable) will exclude preuves
that have fichierId = null; change that innerJoin to a leftJoin (keeping the
same join condition with collectiviteBucketTable.collectiviteId =
bibliothequeFichierTable.collectiviteId) so preuves without files are preserved,
and apply the same change to the identical join in listPreuvesLabellisation to
ensure consistent behavior; if business rules actually require files for all
preuves, confirm and leave as-is.
- Around line 137-143: The catch block in listPreuvesAudit currently returns
CreateLabellisationPreuveErrorEnum.DATABASE_ERROR (from a different feature)
which violates the function's Result<..., ListPreuvesAuditError> contract;
change the returned error enum to the correct
ListPreuvesAuditError.DATABASE_ERROR (and add/import ListPreuvesAuditError if
missing) so the catch returns the proper enum type while keeping the existing
cause/error wrapping logic intact.
In
`@packages/domain/src/collectivites/documents/preuve-audit-with-fichier.schema.ts`:
- Around line 7-12: The schema preuveAuditWithFichierSchema currently extends
bibliothequeFichierSchema only with bucketId but omits the filesize field that
getFileInfoSql() inserts into the SQL JSON; update the fichier schema extension
inside preuveAuditWithFichierSchema to include filesize (e.g. z.number() or
z.nullable(z.number()) as appropriate) so that the SQL-returned filesize is
preserved by Zod parsing; search for preuveAuditWithFichierSchema and
bibliothequeFichierSchema to modify the correct schema and verify
list-preuves.service.ts:getFileInfoSql() and any downstream usages expect the
new filesize property.
🧹 Nitpick comments (7)
apps/backend/src/referentiels/labellisations/get-labellisation.service.ts (1)
157-185: Nouvelle méthodegetAudit: implémentation correcte, duplication à surveiller.La méthode est un miroir fidèle de
getDemande(lines 127-155), ce qui est cohérent. Si d'autres méthodes similaires (getById+NOT_FOUND/DATABASE_ERROR) apparaissent à l'avenir, envisagez d'extraire un helper générique paramétré par la table et le type de retour pour éviter la duplication.♻️ Exemple d'extraction possible (optionnel)
private async getById<T>( table: any, id: number, tx?: Transaction ): Promise<Result<T, GetDemandeOrAuditError>> { try { const rows = await (tx ?? this.db) .select() .from(table) .where(eq(table.id, id)) .limit(1); if (!rows.length) { return { success: false, error: 'NOT_FOUND' }; } return { success: true, data: rows[0] as T }; } catch (error) { this.logger.error(error); return { success: false, error: 'DATABASE_ERROR' }; } }e2e/tests/referentiels/labellisations/validate-labellisation-cot.spec.ts (1)
46-46: Accès à l'auditeur par index magique.
getUser(1)repose sur l'ordre d'ajout des utilisateurs dans lebeforeEach. Si un utilisateur supplémentaire est ajouté en amont dans un futur refactoring, cet index deviendra incorrect silencieusement. Envisagez de stocker l'auditeurUserdans un contexte partagé (fixture ou variable de test) plutôt que de le récupérer par index.apps/app/src/referentiels/audits/useAudit.ts (2)
15-27: Les états de chargement et d'erreur deuseLabellisationParcoursne sont pas propagés.Le hook retourne
{ data: auditEnCours }mais ne distingue pas l'état "pas d'audit en cours" de l'état "données en cours de chargement". SiuseLabellisationParcoursretourneundefinedpendant le chargement,auditEnCoursseranull, identique au cas "pas d'audit". Les consommateurs de ce hook ne peuvent pas différencier ces deux états.
37-45: Appel dupliqué àuseLabellisationParcoursavec les mêmes paramètres queuseAudit.Les hooks
useAuditetuseAuditeursappellent tous deuxuseLabellisationParcoursaveccollectiviteIdetreferentielId. Si le cache React Query sous-jacent est partagé, ce n'est pas un bug, mais la duplication de la logique de récupération du parcours pourrait être factorisée dans un hook commun retournant à la fois l'audit et les auditeurs.apps/backend/src/referentiels/labellisations/list-preuves/list-preuves.router.e2e-spec.ts (1)
70-71: Les assertions sont très superficielles pour un test e2e.
toBeDefined()etArray.isArray()valident uniquement le type de retour. Même un tableau vide passe. Envisagez d'ajouter des vérifications sur la structure des éléments retournés (ex.expect(preuves).toEqual(expect.arrayContaining([...]))) ou au minimumexpect(preuves.length).toBeGreaterThanOrEqual(0)pour documenter le comportement attendu.Also applies to: 90-91
apps/app/src/referentiels/preuves/Bibliotheque/EditerLienModal.tsx (1)
54-60:close()est appelé immédiatement aprèseditLien()sans attendre le résultat de la mutation.Si la mutation échoue, la modale sera déjà fermée et l'utilisateur ne verra pas l'erreur. Ce comportement existait probablement déjà avant ce refactor, mais c'est un point à garder en tête pour une amélioration future (par ex. fermer dans
onSuccessou afficher un toast d'erreur).apps/app/src/referentiels/preuves/Bibliotheque/useEditPreuve.ts (1)
157-164: Garde morte :fichierest requis par le type et ne peut pas être absent.Le type de
preuveà la ligne 157 définitfichier: { hash: string }comme propriété obligatoire. La vérificationif (!preuve?.fichier)à la ligne 162 ne pourra jamais êtretrueau niveau du runtime (sauf contournement de TypeScript). C'est du code mort qui peut induire en erreur.Le même constat s'applique à
useUpdateBibliothequeFichierConfidentiel(lignes 192-194).♻️ Suppression des gardes mortes
mutationFn: async (preuve: { collectivite_id: number; fichier: { hash: string }; updatedFilename: string; }) => { - if (!preuve?.fichier) { - return null; - } const { collectivite_id, fichier, updatedFilename } = preuve;Et de même pour
useUpdateBibliothequeFichierConfidentiel:mutationFn: async (preuve: { collectivite_id: number; fichier: { hash: string }; updatedConfidentiel: boolean; }) => { - if (!preuve?.fichier) { - return null; - } const { collectivite_id, fichier, updatedConfidentiel } = preuve;
| export const useRapportsAudit = (auditId?: number): TPreuveAudit[] => { | ||
| const trpc = useTRPC(); | ||
| const { data: preuvesAudit } = useQuery( | ||
| trpc.referentiels.labellisations.listPreuvesAudit.queryOptions( | ||
| { | ||
| auditId: auditId ?? 0, | ||
| }, | ||
| { | ||
| enabled: Boolean(auditId), | ||
| } | ||
| ) | ||
| ); | ||
| // TODO: fix this | ||
| return (preuvesAudit || []) as unknown as TPreuveAudit[]; | ||
| }; |
There was a problem hiding this comment.
Cast as unknown as TPreuveAudit[] non sûr et type de retour trompeur.
Plusieurs problèmes sur ce hook :
-
Double cast non sûr (ligne 70) :
as unknown as TPreuveAudit[]contourne complètement la vérification de types. Si le type retourné par l'API tRPC diverge deTPreuveAudit, aucune erreur ne sera signalée à la compilation. LeTODOconfirme que c'est un problème connu — il serait préférable de le résoudre maintenant plutôt que de laisser un cast dangereux en production. -
Type de retour synchrone pour des données asynchrones : Le hook retourne
TPreuveAudit[]directement, masquant les états de chargement et d'erreur. Pendant le chargement,[]est retourné, ce qui est indiscernable de "aucune preuve". -
auditId ?? 0(ligne 62) : Passer0comme fallback est un code smell même si le query est désactivé. Sienabledvenait à être retiré par erreur, une requête avecauditId: 0serait exécutée.
♻️ Proposition de refactoring
-export const useRapportsAudit = (auditId?: number): TPreuveAudit[] => {
+export const useRapportsAudit = (auditId?: number) => {
const trpc = useTRPC();
- const { data: preuvesAudit } = useQuery(
+ return useQuery(
trpc.referentiels.labellisations.listPreuvesAudit.queryOptions(
{
- auditId: auditId ?? 0,
+ auditId: auditId!,
},
{
enabled: Boolean(auditId),
}
)
);
- // TODO: fix this
- return (preuvesAudit || []) as unknown as TPreuveAudit[];
};Cela retourne le résultat complet de useQuery (avec data, isLoading, error) et les consommateurs peuvent adapter leur logique. Le type de data correspondra au type réel retourné par l'endpoint tRPC.
🤖 Prompt for AI Agents
In `@apps/app/src/referentiels/audits/useAudit.ts` around lines 57 - 71, The hook
useRapportsAudit currently masks async state and uses an unsafe double-cast on
preuvesAudit; change it to return the full useQuery result (data, isLoading,
error) instead of a plain TPreuveAudit[] so callers can handle loading/errors,
remove the "as unknown as TPreuveAudit[]" cast, and pass auditId to
trpc.referentiels.labellisations.listPreuvesAudit.queryOptions only when defined
(don’t use auditId ?? 0) while keeping the query disabled when auditId is falsy;
ensure the returned type matches the trpc query's inferred type from useTRPC so
no manual casting is needed.
| const { demande } = await createAuditWithOnTestFinished({ | ||
| databaseService: db, | ||
| collectiviteId: collectivite.id, | ||
| referentielId: ReferentielIdEnum.CAE, | ||
| withDemande: true, | ||
| }); | ||
|
|
||
| const caller = router.createCaller({ user: visiteurUser }); | ||
|
|
||
| const preuves = | ||
| await caller.referentiels.labellisations.listPreuvesLabellisation({ | ||
| demandeId: demande!.id, | ||
| }); |
There was a problem hiding this comment.
L'assertion non-null demande!.id pourrait masquer un échec de setup.
Si createAuditWithOnTestFinished retourne demande à undefined (par ex. si withDemande ne fonctionne pas comme prévu), l'erreur obtenue sera un NPE peu explicite. Une assertion préalable rendrait l'échec plus clair :
💡 Proposition
const { demande } = await createAuditWithOnTestFinished({
databaseService: db,
collectiviteId: collectivite.id,
referentielId: ReferentielIdEnum.CAE,
withDemande: true,
});
+ expect(demande).toBeDefined();
+
const caller = router.createCaller({ user: visiteurUser });
const preuves =
await caller.referentiels.labellisations.listPreuvesLabellisation({
- demandeId: demande!.id,
+ demandeId: demande!.id, // safe après l'expect ci-dessus
});📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const { demande } = await createAuditWithOnTestFinished({ | |
| databaseService: db, | |
| collectiviteId: collectivite.id, | |
| referentielId: ReferentielIdEnum.CAE, | |
| withDemande: true, | |
| }); | |
| const caller = router.createCaller({ user: visiteurUser }); | |
| const preuves = | |
| await caller.referentiels.labellisations.listPreuvesLabellisation({ | |
| demandeId: demande!.id, | |
| }); | |
| const { demande } = await createAuditWithOnTestFinished({ | |
| databaseService: db, | |
| collectiviteId: collectivite.id, | |
| referentielId: ReferentielIdEnum.CAE, | |
| withDemande: true, | |
| }); | |
| expect(demande).toBeDefined(); | |
| const caller = router.createCaller({ user: visiteurUser }); | |
| const preuves = | |
| await caller.referentiels.labellisations.listPreuvesLabellisation({ | |
| demandeId: demande!.id, // safe après l'expect ci-dessus | |
| }); |
🤖 Prompt for AI Agents
In
`@apps/backend/src/referentiels/labellisations/list-preuves/list-preuves.router.e2e-spec.ts`
around lines 56 - 68, Add a clear precondition check for the created audit's
demande before using demande!.id: after calling createAuditWithOnTestFinished
(in this test using withDemande: true) assert that demande is defined (e.g.,
expect(demande).toBeDefined() or throw a descriptive error) and only then pass
demande.id into caller.referentiels.labellisations.listPreuvesLabellisation;
reference createAuditWithOnTestFinished, the local variable demande, and the
call to listPreuvesLabellisation/router.createCaller to locate where to add the
assertion so a setup failure surfaces with a clear message rather than a
null-pointer exception.
| .from(preuveAuditTable) | ||
| .leftJoin( | ||
| bibliothequeFichierTable, | ||
| eq(preuveAuditTable.fichierId, bibliothequeFichierTable.id) | ||
| ) | ||
| .innerJoin( | ||
| collectiviteBucketTable, | ||
| eq( | ||
| collectiviteBucketTable.collectiviteId, | ||
| bibliothequeFichierTable.collectiviteId | ||
| ) | ||
| ) | ||
| .leftJoin( | ||
| storageObjectTable, | ||
| and( | ||
| eq(storageObjectTable.bucketId, collectiviteBucketTable.bucketId), | ||
| eq(storageObjectTable.name, bibliothequeFichierTable.hash) | ||
| ) | ||
| ) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n apps/backend/src/referentiels/labellisations/list-preuves/list-preuves.service.ts | head -130Repository: incubateur-ademe/territoires-en-transitions
Length of output: 5941
🏁 Script executed:
sed -n '200,230p' apps/backend/src/referentiels/labellisations/list-preuves/list-preuves.service.tsRepository: incubateur-ademe/territoires-en-transitions
Length of output: 1163
🏁 Script executed:
find . -path "*/models/preuve-audit.table.ts" -o -path "*/models/preuve-labellisation.table.ts" | head -5Repository: incubateur-ademe/territoires-en-transitions
Length of output: 238
🏁 Script executed:
cat -n ./apps/backend/src/collectivites/documents/models/preuve-audit.table.tsRepository: incubateur-ademe/territoires-en-transitions
Length of output: 1145
🏁 Script executed:
find . -path "*/models/document.basetable.ts" -o -path "*/document.basetable*" | head -5Repository: incubateur-ademe/territoires-en-transitions
Length of output: 158
🏁 Script executed:
cat -n ./apps/backend/src/collectivites/documents/models/document.basetable.tsRepository: incubateur-ademe/territoires-en-transitions
Length of output: 897
Le innerJoin sur collectiviteBucketTable exclut silencieusement les preuves sans fichier.
Le fichierId est nullable (DocumentBase, ligne 11), ce qui signifie que les preuves peuvent exister sans fichier attaché. Cependant, le leftJoin sur bibliothequeFichierTable (ligne 102-105) n'empêche pas cette exclusion : lorsqu'il n'y a pas de fichier, le innerJoin sur collectiviteBucketTable (ligne 106-111) échoue car il tente de matcher bibliothequeFichierTable.collectiviteId = NULL, ce qui ne produit aucun résultat.
Ce pattern identique dans listPreuvesLabellisation (lignes 208-218) suggère un comportement potentiellement intentionnel, mais il faut confirmer : est-ce que toutes les preuves d'audit et de labellisation doivent obligatoirement avoir un fichier ? Si non, remplacer le innerJoin par un leftJoin sur collectiviteBucketTable.
🤖 Prompt for AI Agents
In
`@apps/backend/src/referentiels/labellisations/list-preuves/list-preuves.service.ts`
around lines 101 - 119, The innerJoin on collectiviteBucketTable used in the
query starting from preuveAuditTable (the join chain that includes
bibliothequeFichierTable and then collectiviteBucketTable) will exclude preuves
that have fichierId = null; change that innerJoin to a leftJoin (keeping the
same join condition with collectiviteBucketTable.collectiviteId =
bibliothequeFichierTable.collectiviteId) so preuves without files are preserved,
and apply the same change to the identical join in listPreuvesLabellisation to
ensure consistent behavior; if business rules actually require files for all
preuves, confirm and leave as-is.
| return { | ||
| success: false, | ||
| error: CreateLabellisationPreuveErrorEnum.DATABASE_ERROR, | ||
| cause: | ||
| error instanceof Error ? error : new Error(getErrorMessage(error)), | ||
| }; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's locate the file and check its content
fd "list-preuves.service.ts" -type fRepository: incubateur-ademe/territoires-en-transitions
Length of output: 257
🏁 Script executed:
# Let's also look for error enum definitions
fd "list-preuves.*error" -type fRepository: incubateur-ademe/territoires-en-transitions
Length of output: 257
🏁 Script executed:
# Search for ListPreuvesAuditErrorEnum definition
rg "ListPreuvesAuditErrorEnum" --type tsRepository: incubateur-ademe/territoires-en-transitions
Length of output: 664
🏁 Script executed:
# Read the service file around lines 137-143
sed -n '130,150p' apps/backend/src/referentiels/labellisations/list-preuves/list-preuves.service.tsRepository: incubateur-ademe/territoires-en-transitions
Length of output: 666
🏁 Script executed:
# Read the ListPreuvesAuditErrorEnum definition
cat apps/backend/src/referentiels/labellisations/list-preuves/list-preuves-audit.errors.tsRepository: incubateur-ademe/territoires-en-transitions
Length of output: 1143
🏁 Script executed:
# Find and read the CreateLabellisationPreuveErrorEnum definition
fd "list-preuves.*error" | head -20Repository: incubateur-ademe/territoires-en-transitions
Length of output: 268
🏁 Script executed:
# Read the CreateLabellisationPreuveErrorEnum definition
cat apps/backend/src/referentiels/labellisations/list-preuves/list-preuves-labellisation.errors.tsRepository: incubateur-ademe/territoires-en-transitions
Length of output: 1195
🏁 Script executed:
# Check the return type of listPreuvesAudit method
sed -n '44,60p' apps/backend/src/referentiels/labellisations/list-preuves/list-preuves.service.tsRepository: incubateur-ademe/territoires-en-transitions
Length of output: 646
🏁 Script executed:
# Check imports at the top of the service file
head -50 apps/backend/src/referentiels/labellisations/list-preuves/list-preuves.service.ts | grep -i errorRepository: incubateur-ademe/territoires-en-transitions
Length of output: 446
Mauvais enum d'erreur utilisé dans le catch block.
Le type de retour de listPreuvesAudit est Result<..., ListPreuvesAuditError>, mais le bloc catch retourne CreateLabellisationPreuveErrorEnum.DATABASE_ERROR qui provient de l'enum de listPreuvesLabellisation. Bien que les valeurs string coïncident, cela viole le contrat de type et provient d'une copie-colle.
Correction proposée
return {
success: false,
- error: CreateLabellisationPreuveErrorEnum.DATABASE_ERROR,
+ error: ListPreuvesAuditErrorEnum.DATABASE_ERROR,
cause:
error instanceof Error ? error : new Error(getErrorMessage(error)),
};📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| return { | |
| success: false, | |
| error: CreateLabellisationPreuveErrorEnum.DATABASE_ERROR, | |
| cause: | |
| error instanceof Error ? error : new Error(getErrorMessage(error)), | |
| }; | |
| } | |
| return { | |
| success: false, | |
| error: ListPreuvesAuditErrorEnum.DATABASE_ERROR, | |
| cause: | |
| error instanceof Error ? error : new Error(getErrorMessage(error)), | |
| }; | |
| } |
🤖 Prompt for AI Agents
In
`@apps/backend/src/referentiels/labellisations/list-preuves/list-preuves.service.ts`
around lines 137 - 143, The catch block in listPreuvesAudit currently returns
CreateLabellisationPreuveErrorEnum.DATABASE_ERROR (from a different feature)
which violates the function's Result<..., ListPreuvesAuditError> contract;
change the returned error enum to the correct
ListPreuvesAuditError.DATABASE_ERROR (and add/import ListPreuvesAuditError if
missing) so the catch returns the proper enum type while keeping the existing
cause/error wrapping logic intact.
| export const preuveAuditWithFichierSchema = z.extend(preuveAuditSchema, { | ||
| fichier: z.nullable( | ||
| z.extend(bibliothequeFichierSchema, { | ||
| bucketId: z.string(), | ||
| }) | ||
| ), |
There was a problem hiding this comment.
Le champ filesize retourné par le SQL n'est pas présent dans le schéma fichier.
Dans list-preuves.service.ts (ligne 271), getFileInfoSql() inclut 'filesize' dans le json_build_object, mais le schéma fichier ici n'étend bibliothequeFichierSchema qu'avec bucketId. Si ce schéma est utilisé pour la validation (parsing), filesize sera silencieusement ignoré (stripped) par Zod. Si filesize doit être exposé au client, il faut l'ajouter au schéma.
🔧 Correction proposée
fichier: z.nullable(
z.extend(bibliothequeFichierSchema, {
bucketId: z.string(),
+ filesize: z.nullable(z.number()),
})
),📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export const preuveAuditWithFichierSchema = z.extend(preuveAuditSchema, { | |
| fichier: z.nullable( | |
| z.extend(bibliothequeFichierSchema, { | |
| bucketId: z.string(), | |
| }) | |
| ), | |
| export const preuveAuditWithFichierSchema = z.extend(preuveAuditSchema, { | |
| fichier: z.nullable( | |
| z.extend(bibliothequeFichierSchema, { | |
| bucketId: z.string(), | |
| filesize: z.nullable(z.number()), | |
| }) | |
| ), |
🤖 Prompt for AI Agents
In
`@packages/domain/src/collectivites/documents/preuve-audit-with-fichier.schema.ts`
around lines 7 - 12, The schema preuveAuditWithFichierSchema currently extends
bibliothequeFichierSchema only with bucketId but omits the filesize field that
getFileInfoSql() inserts into the SQL JSON; update the fichier schema extension
inside preuveAuditWithFichierSchema to include filesize (e.g. z.number() or
z.nullable(z.number()) as appropriate) so that the SQL-returned filesize is
preserved by Zod parsing; search for preuveAuditWithFichierSchema and
bibliothequeFichierSchema to modify the correct schema and verify
list-preuves.service.ts:getFileInfoSql() and any downstream usages expect the
new filesize property.
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In
`@apps/backend/src/referentiels/labellisations/start-audit/start-audit.errors.ts`:
- Around line 7-11: specificErrors currently duplicates 'AUDIT_NOT_FOUND'
(already present in startAuditRulesErrors) and omits 'UNAUTHORIZED' which is
referenced by StartAuditErrorEnum.UNAUTHORIZED; remove the duplicate
'AUDIT_NOT_FOUND' entry from specificErrors and add 'UNAUTHORIZED' to that array
(so it becomes a valid enum member), then add the corresponding configuration
for 'UNAUTHORIZED' in startAuditErrorConfig to match other entries (use the same
shape as existing configs) so the service can resolve
StartAuditErrorEnum.UNAUTHORIZED.
In
`@apps/backend/src/referentiels/labellisations/start-audit/start-audit.router.e2e-spec.ts`:
- Around line 123-144: The test currently uses yoloDodoUser as caller but grants
auditeur rights to lectureUser, so it asserts a permissions error instead of
"already started"; update the test to call
caller.referentiels.labellisations.startAudit with caller =
router.createCaller({ user: lectureUser }) (so the auditeur invokes startAudit),
ensure the audit is in a started state (use the existing
createAuditWithOnTestFinished setup or explicitly mark the audit started), and
change the expectation to assert the restart-specific error message (e.g.,
toThrow(/déjà démarré|already started/i)) instead of the permissions error.
In
`@apps/backend/src/referentiels/labellisations/start-audit/start-audit.service.ts`:
- Around line 76-82: The update call that sets dateDebut on auditTable may
return no rows, so startedAudit (from update(auditTable)...returning().then(rows
=> rows[0])) can be undefined; add an explicit check after that promise resolves
and, if undefined, throw a clear NotFound/Conflict error (e.g.,
AuditNotFoundError or a BadRequest with message "Audit not found or already
deleted") instead of proceeding to access its properties, so TOCTOU cases are
handled with an explicit error rather than a downstream DATABASE_ERROR.
🧹 Nitpick comments (4)
apps/backend/src/referentiels/labellisations/start-audit/start-audit.router.e2e-spec.ts (2)
23-23:RANDOM_COLLECTIVITE_IDsemble inutilisé.Cette constante n'est plus référencée dans le fichier après la migration vers les fixtures dynamiques. Elle peut être supprimée.
♻️ Suppression du code mort
-const RANDOM_COLLECTIVITE_ID = 19; -
56-59: L'accès par index aux utilisateurs est fragile.L'accès à
users[0]etusers[1]dépend de l'ordre du tableauuserspassé àaddTestCollectiviteAndUsers. Si l'ordre change, les rôles seront silencieusement inversés et les tests passeront ou échoueront de manière confuse. Envisagez une extraction plus explicite, par exemple via le rôle ou l'email.packages/domain/src/referentiels/labellisations/start-audit/start-audit.rules.ts (1)
4-41: Fonction bien structurée avec un type de retour discriminé clair.Considération mineure : les raisons d'erreur sont écrites en tant que littéraux de chaîne (
'AUDIT_NOT_FOUND', etc.) plutôt que viaStartAuditRulesErrorsEnum. C'est type-safe grâce à l'annotation du type de retour, mais utiliser l'enum-object importé rendrait le code plus résistant aux fautes de frappe et cohérent avec la convention du projet.♻️ Utilisation optionnelle de l'enum-object
import { ParcoursLabellisation } from '../parcours-labellisation.schema'; -import { StartAuditRulesErrors } from './start-audit.rules-errors'; +import { StartAuditRulesErrors, StartAuditRulesErrorsEnum } from './start-audit.rules-errors'; // ... if (!parcours) { return { canRequest: false, - reason: 'AUDIT_NOT_FOUND', + reason: StartAuditRulesErrorsEnum.AUDIT_NOT_FOUND, }; }apps/backend/src/referentiels/labellisations/start-audit/start-audit.service.ts (1)
84-93: Le TODO sur la transaction est pertinent.Si
computeAndUpsertéchoue,dateDebutest déjà persisté sans snapshotPRE_AUDIT. Lecatchglobal capturera l'erreur et retourneraDATABASE_ERROR, mais l'état de l'audit sera incohérent (démarré sans snapshot). Envisagez de regrouper ces opérations dans une transaction pour garantir la cohérence.
| const specificErrors = [ | ||
| ...startAuditRulesErrors, | ||
| 'AUDIT_NOT_FOUND', | ||
| 'DATABASE_ERROR', | ||
| ] as const; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Vérifier l'utilisation d'UNAUTHORIZED dans le service
rg -n 'UNAUTHORIZED' apps/backend/src/referentiels/labellisations/start-audit/ -A 2 -B 2
# Vérifier la définition de startAuditRulesErrors
rg -n 'startAuditRulesErrors' apps/backend/src/referentiels/labellisations/start-audit/ -A 3 -B 1
# Lister les fichiers du répertoire pour avoir une vue d'ensemble
fd . apps/backend/src/referentiels/labellisations/start-audit/ --type f -name '*.ts'Repository: incubateur-ademe/territoires-en-transitions
Length of output: 1738
🏁 Script executed:
# Vérifier la définition de startAuditRulesErrors dans le domaine
rg 'export.*startAuditRulesErrors' -A 5 apps/backend/
# Vérifier le contenu du fichier errors complet
cat -n apps/backend/src/referentiels/labellisations/start-audit/start-audit.errors.ts
# Vérifier la configuration d'erreur complète
cat -n apps/backend/src/referentiels/labellisations/start-audit/start-audit.error-config.tsRepository: incubateur-ademe/territoires-en-transitions
Length of output: 1610
🏁 Script executed:
# Chercher la définition de startAuditRulesErrors dans le domaine
fd startAuditRulesErrors apps/backend/
# Chercher dans le domaine en général
rg 'startAuditRulesErrors' apps/backend/ -B 2 -A 2
# Aussi chercher dans `@tet/domain`
fd '@tet/domain' apps/ --type dRepository: incubateur-ademe/territoires-en-transitions
Length of output: 958
🏁 Script executed:
# Chercher la structure générale du repo
git ls-files | head -30
# Chercher startAuditRulesErrors partout dans le repo
rg 'startAuditRulesErrors' -g '*.ts' -B 1 -A 5
# Chercher dans les répertoires de packages/libs
find . -name '@tet' -o -name 'domain' -type d 2>/dev/null | head -20Repository: incubateur-ademe/territoires-en-transitions
Length of output: 3891
🏁 Script executed:
# Voir le contenu du service autour de la ligne 50 pour comprendre le contexte d'UNAUTHORIZED
cat -n apps/backend/src/referentiels/labellisations/start-audit/start-audit.service.ts | sed -n '40,60p'
# Vérifier si UNAUTHORIZED est utilisé ailleurs
rg 'UNAUTHORIZED' apps/backend/src/referentiels/labellisations/start-audit/ -B 5 -A 5Repository: incubateur-ademe/territoires-en-transitions
Length of output: 1828
'AUDIT_NOT_FOUND' est dupliqué et 'UNAUTHORIZED' est manquant.
Deux problèmes critiques :
-
Duplication :
startAuditRulesErrorscontient déjà'AUDIT_NOT_FOUND'(cf.packages/domain/src/referentiels/labellisations/start-audit/start-audit.rules-errors.ts), il est donc dupliqué à la ligne 9. -
Erreur manquante : Le service (ligne 50) référence
StartAuditErrorEnum.UNAUTHORIZED, mais'UNAUTHORIZED'n'existe pas dansspecificErrorset n'a pas de configuration dansstartAuditErrorConfig. Cela rendraUNAUTHORIZEDinvalide en tant que membre de l'énumération.
Correction proposée
const specificErrors = [
...startAuditRulesErrors,
- 'AUDIT_NOT_FOUND',
+ 'UNAUTHORIZED',
'DATABASE_ERROR',
] as const;Et ajouter la configuration pour UNAUTHORIZED :
export const startAuditErrorConfig: TrpcErrorHandlerConfig<SpecificError> = {
specificErrors: {
+ UNAUTHORIZED: {
+ code: 'FORBIDDEN',
+ message: "L'utilisateur n'a pas les permissions nécessaires pour démarrer cet audit.",
+ },
AUDIT_NOT_FOUND: {🤖 Prompt for AI Agents
In
`@apps/backend/src/referentiels/labellisations/start-audit/start-audit.errors.ts`
around lines 7 - 11, specificErrors currently duplicates 'AUDIT_NOT_FOUND'
(already present in startAuditRulesErrors) and omits 'UNAUTHORIZED' which is
referenced by StartAuditErrorEnum.UNAUTHORIZED; remove the duplicate
'AUDIT_NOT_FOUND' entry from specificErrors and add 'UNAUTHORIZED' to that array
(so it becomes a valid enum member), then add the corresponding configuration
for 'UNAUTHORIZED' in startAuditErrorConfig to match other entries (use the same
shape as existing configs) so the service can resolve
StartAuditErrorEnum.UNAUTHORIZED.
| test('On ne peut pas lancer un audit déjà démarré', async () => { | ||
| const caller = router.createCaller({ user: yoloDodoUser }); | ||
|
|
||
| const { audit } = await createAuditWithOnTestFinished({ | ||
| databaseService: db, | ||
| collectiviteId: collectivite.id, | ||
| referentielId: ReferentielIdEnum.CAE, | ||
| withDemande: true, | ||
| }); | ||
|
|
||
| await addAuditeurPermission({ | ||
| databaseService: db, | ||
| auditId: audit.id, | ||
| userId: lectureUser.id, | ||
| }); | ||
|
|
||
| await expect( | ||
| caller.referentiels.labellisations.startAudit({ | ||
| auditId: audit.id, | ||
| }) | ||
| ).rejects.toThrow(/Vous n'avez pas les permissions nécessaires/i); | ||
| }); |
There was a problem hiding this comment.
Le test ne vérifie pas ce que son nom indique.
Le test s'intitule « On ne peut pas lancer un audit déjà démarré », mais le caller est yoloDodoUser (ligne 124) qui n'est pas l'auditeur de cet audit — c'est lectureUser qui est ajouté comme auditeur (ligne 136). L'erreur capturée est donc une erreur de permissions, pas une erreur liée au fait que l'audit est déjà démarré.
Pour tester réellement le scénario « audit déjà démarré », le caller devrait être lectureUser (l'auditeur) et l'assertion devrait vérifier un message d'erreur spécifique au redémarrage.
🐛 Correction proposée
test('On ne peut pas lancer un audit déjà démarré', async () => {
- const caller = router.createCaller({ user: yoloDodoUser });
-
const { audit } = await createAuditWithOnTestFinished({
databaseService: db,
collectiviteId: collectivite.id,
referentielId: ReferentielIdEnum.CAE,
withDemande: true,
});
await addAuditeurPermission({
databaseService: db,
auditId: audit.id,
userId: lectureUser.id,
});
+ const caller = router.createCaller({ user: lectureUser });
+
await expect(
caller.referentiels.labellisations.startAudit({
auditId: audit.id,
})
- ).rejects.toThrow(/Vous n'avez pas les permissions nécessaires/i);
+ ).rejects.toThrow(/déjà démarré|already started/i);
});📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| test('On ne peut pas lancer un audit déjà démarré', async () => { | |
| const caller = router.createCaller({ user: yoloDodoUser }); | |
| const { audit } = await createAuditWithOnTestFinished({ | |
| databaseService: db, | |
| collectiviteId: collectivite.id, | |
| referentielId: ReferentielIdEnum.CAE, | |
| withDemande: true, | |
| }); | |
| await addAuditeurPermission({ | |
| databaseService: db, | |
| auditId: audit.id, | |
| userId: lectureUser.id, | |
| }); | |
| await expect( | |
| caller.referentiels.labellisations.startAudit({ | |
| auditId: audit.id, | |
| }) | |
| ).rejects.toThrow(/Vous n'avez pas les permissions nécessaires/i); | |
| }); | |
| test('On ne peut pas lancer un audit déjà démarré', async () => { | |
| const { audit } = await createAuditWithOnTestFinished({ | |
| databaseService: db, | |
| collectiviteId: collectivite.id, | |
| referentielId: ReferentielIdEnum.CAE, | |
| withDemande: true, | |
| }); | |
| await addAuditeurPermission({ | |
| databaseService: db, | |
| auditId: audit.id, | |
| userId: lectureUser.id, | |
| }); | |
| const caller = router.createCaller({ user: lectureUser }); | |
| await expect( | |
| caller.referentiels.labellisations.startAudit({ | |
| auditId: audit.id, | |
| }) | |
| ).rejects.toThrow(/déjà démarré|already started/i); | |
| }); |
🤖 Prompt for AI Agents
In
`@apps/backend/src/referentiels/labellisations/start-audit/start-audit.router.e2e-spec.ts`
around lines 123 - 144, The test currently uses yoloDodoUser as caller but
grants auditeur rights to lectureUser, so it asserts a permissions error instead
of "already started"; update the test to call
caller.referentiels.labellisations.startAudit with caller =
router.createCaller({ user: lectureUser }) (so the auditeur invokes startAudit),
ensure the audit is in a started state (use the existing
createAuditWithOnTestFinished setup or explicitly mark the audit started), and
change the expectation to assert the restart-specific error message (e.g.,
toThrow(/déjà démarré|already started/i)) instead of the permissions error.
| // Update audit `date_debut` | ||
| const startedAudit = await this.db | ||
| .update(auditTable) | ||
| .set({ dateDebut: sql`now()` }) | ||
| .where(eq(auditTable.id, auditId)) | ||
| .returning() | ||
| .then((rows) => rows[0]); |
There was a problem hiding this comment.
rows[0] peut être undefined si aucune ligne n'est mise à jour.
Si l'audit a été supprimé entre la vérification et la mise à jour (TOCTOU), startedAudit sera undefined et les accès aux propriétés lignes 89-92 lèveront une erreur. Le catch global la transformera en DATABASE_ERROR, mais un message plus explicite serait préférable.
🛡️ Vérification proposée
const startedAudit = await this.db
.update(auditTable)
.set({ dateDebut: sql`now()` })
.where(eq(auditTable.id, auditId))
.returning()
.then((rows) => rows[0]);
+ if (!startedAudit) {
+ return {
+ success: false,
+ error: StartAuditErrorEnum.AUDIT_NOT_FOUND,
+ };
+ }
+
// TODO it could be great to create a transaction...🤖 Prompt for AI Agents
In
`@apps/backend/src/referentiels/labellisations/start-audit/start-audit.service.ts`
around lines 76 - 82, The update call that sets dateDebut on auditTable may
return no rows, so startedAudit (from update(auditTable)...returning().then(rows
=> rows[0])) can be undefined; add an explicit check after that promise resolves
and, if undefined, throw a clear NotFound/Conflict error (e.g.,
AuditNotFoundError or a BadRequest with message "Audit not found or already
deleted") instead of proceeding to access its properties, so TOCTOU cases are
handled with an explicit error rather than a downstream DATABASE_ERROR.
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Fix all issues with AI agents
In
`@apps/app/app/`(authed)/collectivite/[collectiviteId]/(acces-restreint)/referentiel/[referentielId]/action/[actionId]/_components/task/task-card.tsx:
- Around line 31-34: The data-test attribute can become "Tache-null" because
getIdentifiantFromActionId(task.id) may return null; update the JSX that sets
data-test so it guards the result of getIdentifiantFromActionId and uses a safe
fallback (for example the original task.id or an empty string) instead of
letting "null" appear; locate the usage in task-card.tsx where
data-test={`Tache-${getIdentifiantFromActionId(task.id)}`} is set and change it
to compute an identifier variable (e.g., const ident =
getIdentifiantFromActionId(task.id) ?? task.id ?? '') and use
data-test={`Tache-${ident}`} to ensure e2e selectors never contain "null".
In `@apps/app/src/referentiels/actions/action-statut/use-action-statut.ts`:
- Around line 28-34: The current call to findActionById when computing
actionScore can throw a ReferentielException and crash rendering; replace it
with a safe lookup using findActionInTree (which returns undefined when not
found) or wrap the findActionById call in a try/catch and treat failures as
null/undefined before calling getActionStatutFromActionScore; update the
variable path where actionScore is computed and ensure
getActionStatutFromActionScore is only invoked when actionScore is truthy so the
component no longer throws on missing action IDs.
In `@packages/domain/src/referentiels/referentiel.utils.ts`:
- Around line 384-390: The computed variable `filled` can become undefined
because the expression mixes &&/|| with possibly undefined `avancement` and an
empty `actionsEnfant`, violating the declared boolean type; update the
calculation for `filled` in referentiel.utils.ts (the `filled` const that reads
from `actionScore?.score.avancement` and `actionScore?.actionsEnfant.some(...)`)
to explicitly coerce the result to a boolean (e.g., wrap the whole expression
with a Boolean(...) or prefix with !!) and guard against a missing
`actionsEnfant` by using a safe optional
(`actionScore?.actionsEnfant?.some(...)`) so `filled` is always true or false.
- Around line 373-383: The code assigns modifiedAt:
actionScore.score.statutModifiedAt || '' which yields an empty string that fails
the ActionStatut schema (modifiedAt: z.iso.datetime()); to fix, update the
ActionStatut schema to allow nullable datetimes (change modifiedAt to
z.nullable(z.iso.datetime())), and in the referentiel.utils.ts construction of
actionStatut set modifiedAt to actionScore.score.statutModifiedAt || null so the
runtime value matches the schema; ensure any consumers/validators handle the
nullable ISO datetime accordingly.
🧹 Nitpick comments (1)
apps/backend/src/referentiels/compute-score/scores.service.ts (1)
365-368: Affectation dupliquée destatutModifiedBy/statutModifiedAtdans deux méthodes.Ces deux champs sont assignés dans
appliqueActionStatutNonConcerneEtRemonteAuxParents(ligne 365-368) et dansappliqueActionAvancementEtRemonteAuxParents(ligne 567-570). Les deux méthodes sont appelées séquentiellement danscomputeScore(lignes 1877 puis 1892). Pour les actions concernées ayant un statut, les valeurs sont écrites deux fois de manière identique.Ce n'est pas un bug (les valeurs sont les mêmes), mais pour la lisibilité il serait préférable de n'assigner ces champs qu'à un seul endroit — idéalement dans
appliqueActionStatutNonConcerneEtRemonteAuxParentsqui est déjà le premier passage et qui couvre tous les cas (concerné ou non).♻️ Suppression de la duplication dans `appliqueActionAvancementEtRemonteAuxParents`
if (actionStatut) { referentielActionAvecScore.score.aStatut = true; referentielActionAvecScore.score.avancement = actionStatut.avancement; - referentielActionAvecScore.score.statutModifiedBy = - actionStatut.modifiedBy; - referentielActionAvecScore.score.statutModifiedAt = - actionStatut.modifiedAt; }Also applies to: 567-570
| <div | ||
| data-test={`Tache-${getIdentifiantFromActionId(task.id)}`} | ||
| className="flex flex-col gap-2 bg-grey-1 border border-grey-3 rounded-lg p-4" | ||
| > |
There was a problem hiding this comment.
Attribut data-test potentiellement "Tache-null"
getIdentifiantFromActionId peut retourner null, ce qui produirait data-test="Tache-null". En pratique, les IDs de tâches devraient toujours avoir le format attendu (cae_X.X.X), donc le risque est faible, mais cela pourrait rendre les sélecteurs e2e silencieusement incorrects si un ID inattendu se présente.
🤖 Prompt for AI Agents
In
`@apps/app/app/`(authed)/collectivite/[collectiviteId]/(acces-restreint)/referentiel/[referentielId]/action/[actionId]/_components/task/task-card.tsx
around lines 31 - 34, The data-test attribute can become "Tache-null" because
getIdentifiantFromActionId(task.id) may return null; update the JSX that sets
data-test so it guards the result of getIdentifiantFromActionId and uses a safe
fallback (for example the original task.id or an empty string) instead of
letting "null" appear; locate the usage in task-card.tsx where
data-test={`Tache-${getIdentifiantFromActionId(task.id)}`} is set and change it
to compute an identifier variable (e.g., const ident =
getIdentifiantFromActionId(task.id) ?? task.id ?? '') and use
data-test={`Tache-${ident}`} to ensure e2e selectors never contain "null".
| const actionScore = snapshot?.scoresPayload.scores | ||
| ? findActionById(snapshot?.scoresPayload.scores, actionId) | ||
| : null; | ||
|
|
||
| const filled = | ||
| data?.find( | ||
| (action) => | ||
| action.actionId.includes(actionId) && | ||
| action.actionId.split(actionId)[1] !== '' && | ||
| action.avancement !== 'non_renseigne' | ||
| ) !== undefined || null; | ||
| const actionStatutFromScore = actionScore | ||
| ? getActionStatutFromActionScore(collectiviteId, actionScore) | ||
| : null; |
There was a problem hiding this comment.
findActionById peut lever une exception dans le chemin de rendu
findActionById lève une ReferentielException si l'action n'est pas trouvée dans l'arbre (cf. referentiel.utils.ts lignes 274-277). Si le snapshot est chargé mais que l'actionId n'existe pas dans l'arbre des scores (données désynchronisées, erreur, etc.), cela crashera le composant React.
Envisagez d'utiliser findActionInTree directement (qui retourne undefined) ou d'envelopper l'appel dans un try/catch.
🛡️ Correction proposée avec findActionInTree
+import {
+ canUpdateActionStatutWithoutPermissionCheck,
+ findActionInTree,
+ getActionStatutFromActionScore,
+} from '@tet/domain/referentiels';
+
...
- const actionScore = snapshot?.scoresPayload.scores
- ? findActionById(snapshot?.scoresPayload.scores, actionId)
- : null;
+ const actionScore = snapshot?.scoresPayload.scores
+ ? findActionInTree(
+ [snapshot.scoresPayload.scores],
+ (a) => a.actionId === actionId
+ ) ?? null
+ : null;🤖 Prompt for AI Agents
In `@apps/app/src/referentiels/actions/action-statut/use-action-statut.ts` around
lines 28 - 34, The current call to findActionById when computing actionScore can
throw a ReferentielException and crash rendering; replace it with a safe lookup
using findActionInTree (which returns undefined when not found) or wrap the
findActionById call in a try/catch and treat failures as null/undefined before
calling getActionStatutFromActionScore; update the variable path where
actionScore is computed and ensure getActionStatutFromActionScore is only
invoked when actionScore is truthy so the component no longer throws on missing
action IDs.
| const { avancement } = actionScore.score; | ||
|
|
||
| const actionStatut: ActionStatut = { | ||
| collectiviteId: collectiviteId, | ||
| actionId: actionScore.actionId, | ||
| avancement: avancement || 'non_renseigne', | ||
| avancementDetaille: null, | ||
| concerne: actionScore.score.concerne, | ||
| modifiedBy: actionScore.score.statutModifiedBy || null, | ||
| modifiedAt: actionScore.score.statutModifiedAt || '', | ||
| }; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Rechercher la définition du type ActionStatut
find . -type f \( -name "*.ts" -o -name "*.tsx" \) | xargs grep -l "ActionStatut" | grep -E "(schema|interface|type)" | head -20Repository: incubateur-ademe/territoires-en-transitions
Length of output: 351
🏁 Script executed:
# Rechercher le type modifiedAt dans ActionStatut
rg -n "ActionStatut|modifiedAt" --type=ts -B 2 -A 2 | grep -A 10 -B 10 "modifiedAt" | head -50Repository: incubateur-ademe/territoires-en-transitions
Length of output: 3560
🏁 Script executed:
# Rechercher les fichiers contenant la définition de ActionStatut
fd -e ts -e tsx | xargs rg "export (interface|type|class|schema) ActionStatut" -A 20 | head -100Repository: incubateur-ademe/territoires-en-transitions
Length of output: 1573
🏁 Script executed:
cat -n packages/domain/src/referentiels/actions/action-statut.schema.tsRepository: incubateur-ademe/territoires-en-transitions
Length of output: 992
🏁 Script executed:
# Chercher où les objets ActionStatut sont créés/validés
rg -n "actionStatutSchema\|ActionStatut" --type=ts packages/domain/src/referentiels/actions/ -B 2 -A 2Repository: incubateur-ademe/territoires-en-transitions
Length of output: 69
🏁 Script executed:
# Chercher l'utilisation du code en ligne 373-383
rg -n "actionScore.score.statutModifiedAt" --type=tsRepository: incubateur-ademe/territoires-en-transitions
Length of output: 202
🏁 Script executed:
# Chercher si modifiedAt est nullable ailleurs
rg -n "modifiedAt.*nullable\|nullable.*modifiedAt" --type=tsRepository: incubateur-ademe/territoires-en-transitions
Length of output: 69
🏁 Script executed:
# Vérifier le contexte complet de la ligne 382 et où modifiedAt est utilisé
cat -n packages/domain/src/referentiels/referentiel.utils.ts | sed -n '370,390p'Repository: incubateur-ademe/territoires-en-transitions
Length of output: 909
🏁 Script executed:
# Chercher où ActionStatut est validé/utilisé
rg -n "actionStatutSchema\\.parse\|actionStatutSchema\\.safeParse" --type=tsRepository: incubateur-ademe/territoires-en-transitions
Length of output: 69
modifiedAt défini à une chaîne vide — incohérence avec le schéma de type
Ligne 382 : modifiedAt: actionScore.score.statutModifiedAt || '' assigne une chaîne vide si la valeur est absente. Le schéma ActionStatut (packages/domain/src/referentiels/actions/action-statut.schema.ts:11) définit modifiedAt: z.iso.datetime(), ce qui exige une date ISO valide — une chaîne vide n'est pas une valeur ISO valide et causera une erreur de validation.
Contrairement à modifiedBy qui utilise z.nullable(z.uuid()), modifiedAt n'est pas nullable dans le schéma. La correction appropriée est de :
- Soit changer le schéma pour permettre null :
z.nullable(z.iso.datetime()) - Soit fournir toujours une date ISO valide (par exemple une date par défaut ou la date actuelle)
- modifiedAt: actionScore.score.statutModifiedAt || '',
+ modifiedAt: actionScore.score.statutModifiedAt || null,Remarque : cette correction requiert également de rendre modifiedAt nullable dans le schéma.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const { avancement } = actionScore.score; | |
| const actionStatut: ActionStatut = { | |
| collectiviteId: collectiviteId, | |
| actionId: actionScore.actionId, | |
| avancement: avancement || 'non_renseigne', | |
| avancementDetaille: null, | |
| concerne: actionScore.score.concerne, | |
| modifiedBy: actionScore.score.statutModifiedBy || null, | |
| modifiedAt: actionScore.score.statutModifiedAt || '', | |
| }; | |
| const { avancement } = actionScore.score; | |
| const actionStatut: ActionStatut = { | |
| collectiviteId: collectiviteId, | |
| actionId: actionScore.actionId, | |
| avancement: avancement || 'non_renseigne', | |
| avancementDetaille: null, | |
| concerne: actionScore.score.concerne, | |
| modifiedBy: actionScore.score.statutModifiedBy || null, | |
| modifiedAt: actionScore.score.statutModifiedAt || null, | |
| }; |
🤖 Prompt for AI Agents
In `@packages/domain/src/referentiels/referentiel.utils.ts` around lines 373 -
383, The code assigns modifiedAt: actionScore.score.statutModifiedAt || '' which
yields an empty string that fails the ActionStatut schema (modifiedAt:
z.iso.datetime()); to fix, update the ActionStatut schema to allow nullable
datetimes (change modifiedAt to z.nullable(z.iso.datetime())), and in the
referentiel.utils.ts construction of actionStatut set modifiedAt to
actionScore.score.statutModifiedAt || null so the runtime value matches the
schema; ensure any consumers/validators handle the nullable ISO datetime
accordingly.
| const filled = | ||
| (actionScore?.score.avancement && | ||
| actionScore?.score.avancement !== 'non_renseigne') || | ||
| actionScore?.actionsEnfant.some( | ||
| (action) => | ||
| action.score.avancement && action.score.avancement !== 'non_renseigne' | ||
| ); |
There was a problem hiding this comment.
filled peut être undefined au lieu de boolean
L'expression utilise des opérateurs && et || avec des valeurs potentiellement undefined. Si avancement est undefined et actionsEnfant est vide, filled sera undefined au lieu de false. Le type de retour déclare filled: boolean, ce qui est incohérent.
🐛 Correction proposée
- const filled =
- (actionScore?.score.avancement &&
- actionScore?.score.avancement !== 'non_renseigne') ||
- actionScore?.actionsEnfant.some(
- (action) =>
- action.score.avancement && action.score.avancement !== 'non_renseigne'
- );
+ const filled =
+ (!!actionScore.score.avancement &&
+ actionScore.score.avancement !== 'non_renseigne') ||
+ !!actionScore.actionsEnfant.some(
+ (action) =>
+ action.score.avancement && action.score.avancement !== 'non_renseigne'
+ );📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const filled = | |
| (actionScore?.score.avancement && | |
| actionScore?.score.avancement !== 'non_renseigne') || | |
| actionScore?.actionsEnfant.some( | |
| (action) => | |
| action.score.avancement && action.score.avancement !== 'non_renseigne' | |
| ); | |
| const filled = | |
| (!!actionScore.score.avancement && | |
| actionScore.score.avancement !== 'non_renseigne') || | |
| !!actionScore.actionsEnfant.some( | |
| (action) => | |
| action.score.avancement && action.score.avancement !== 'non_renseigne' | |
| ); |
🤖 Prompt for AI Agents
In `@packages/domain/src/referentiels/referentiel.utils.ts` around lines 384 -
390, The computed variable `filled` can become undefined because the expression
mixes &&/|| with possibly undefined `avancement` and an empty `actionsEnfant`,
violating the declared boolean type; update the calculation for `filled` in
referentiel.utils.ts (the `filled` const that reads from
`actionScore?.score.avancement` and `actionScore?.actionsEnfant.some(...)`) to
explicitly coerce the result to a boolean (e.g., wrap the whole expression with
a Boolean(...) or prefix with !!) and guard against a missing `actionsEnfant` by
using a safe optional (`actionScore?.actionsEnfant?.some(...)`) so `filled` is
always true or false.
25143fb to
f3343b4
Compare
… suppression de rpc audit_en_cours, audit_auditeurs
…lectivités et les user infos
…, utilisation du listing des preuves d'audit en tRPC
…e tests du listing des preuves, ajout de tests de démarrage d'audit
…tement les snapshots
…ommentaires et tests associés
1c7eab9 to
f4c639c
Compare
…nent pas avec le RichTextEditor
… suppression de rpc audit_en_cours, audit_auditeurs
Summary by CodeRabbit
Tests
Refactor
New Features
Chores
Robustesse