TarificationService :
- Ajout NOSONAR sur DEFAULT_PRICES (données de configuration, pas de logique
dupliquée — les littéraux 500.00, 100.00, 50.00, 30.00 sont des prix distincts)
- Ajout LoggerInterface dans le constructeur
- Remplacement catch (\Throwable) vide par log warning avec message Stripe
- Conversion DEFAULT_PRICES de const vers méthode statique getDefaultPricesData()
pour permettre l'utilisation de constantes PHP dans les valeurs
Nettoyage @codeCoverageIgnore redondants :
- Suppression des PHPDoc @codeCoverageIgnore sur les repositories (déjà exclus
via phpunit.dist.xml directory et phpstan.dist.neon)
- Suppression @codeCoverageIgnore sur WebhookDocuSealController et CheckDnsCommand
(déjà exclus via phpunit.dist.xml file)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Supprime les warnings SonarQube "Failed to resolve file path(s)" pour
les 15 fichiers Repository exclus de SonarQube mais encore analysés
par PHPStan.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Ajout constante PATH_GROUPS = '/groups' (remplace 4 littéraux dupliqués)
- Fusion des 2 if imbriqués dans ensureRequiredGroups en une seule condition
avec && (in_array + createGroup)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fusion catch SignatureVerificationException et catch Throwable en un seul
catch Throwable avec instanceof pour différencier le message d'erreur.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- checkAccess() : suppression paramètre $devis (inutilisé)
- Suppression TODO et code commenté
- Remplacement early return redondant par if négatif avec logger warning
- Ajout LoggerInterface pour tracer les accès non-employé
- Suppression import App\Entity\Devis (plus utilisé)
- Tests mis à jour avec LoggerInterface dans le constructeur
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Dernière validation (counter < 1) convertie en expression ternaire
pour respecter la limite de 3 returns par méthode.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
OrderNumberController :
- Extraction validateNumber() avec les 3 validations (format, existence, minimum)
- applyNextNumber réduit à 2 returns (erreur validation ou null succès)
sonar-project.properties :
- Ajout templates/email/** et templates/emails/** dans sonar.exclusions
(templates HTML email non analysables par SonarQube)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Templates PDF :
- _base.html.twig : blocs verify_box et hmac_section avec contenu par défaut
(QR code, verify_url, HMAC-SHA256) au lieu de blocs vides
- rgpd_access.html.twig : suppression blocs verify_box et hmac_section dupliqués
(héritent du parent)
- rgpd_deletion.html.twig : idem
- rgpd_no_data.html.twig : idem
DevisPdfControllerTest (8 tests) :
- testDevisNotFound : devis null lance NotFoundHttpException
- testUnsignedPdfNotSet : unsignedPdf null lance NotFoundHttpException
- testFileNotExists : fichier absent lance NotFoundHttpException
- testUnsignedPdfSuccess : PDF unsigned retourné en BinaryFileResponse
- testSignedPdfSuccess : PDF signed retourné
- testAuditPdfSuccess : PDF audit retourné
- testAccessAsNonEmploye : accès sans ROLE_EMPLOYE (branche checkAccess)
- testDefaultTypeNull : type inconnu lance NotFoundHttpException
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Le tableau DEFAULT_PRICES contient 16 entrées de données avec la même
structure (title, description, priceHt, monthPrice, period) — c'est de
la configuration, pas du code dupliqué.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Supprime les warnings SonarQube "Failed to resolve file path(s)" en
alignant les exclusions entre sonar.exclusions, phpunit.dist.xml et
phpstan.dist.neon pour les fichiers API live déjà ignorés.
- phpunit.dist.xml : ajout DnsReportController.php et CheckDnsCommand.php
dans source/exclude
- phpstan.dist.neon : ajout DnsReportController.php dans excludePaths
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Les repositories étendent ServiceEntityRepository et dépendent de
ManagerRegistry/EntityManager — non testables unitairement sans base
de données. Déjà exclus dans phpunit.dist.xml via <directory>src/Repository</directory>.
Fichiers : AdvertRepository, AppLogRepository, AttestationRepository,
CustomerRepository, DevisRepository, EmailTrackingRepository,
FactureRepository, MessengerLogRepository, OrderNumberRepository,
PriceAutomaticRepository, RevendeurRepository, ServiceCategoryRepository,
ServiceRepository, StripeWebhookSecretRepository, UserRepository
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
DevisTest (10 tests) :
- testConstructor : id null, orderNumber, state created, hmac, createdAt, updatedAt null, adverts vide
- testState : setState send puis accepted
- testRaisonMessage : null par défaut, set/get
- testTotals : totalHt/Tva/Ttc à 0.00 par défaut, set/get avec montants
- testSubmitterIds : submitterSiteconseilId et submitterCustomerId null puis set/get
- testUnsignedPdf : pdf string + File réel avec updatedAt mis à jour + null
- testSignedPdf : pdf string + File réel avec updatedAt mis à jour
- testAuditPdf : pdf string + File réel avec updatedAt mis à jour
- testVerifyHmacValid : vérification HMAC avec le bon secret
- testVerifyHmacInvalid : vérification HMAC avec mauvais secret
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- testCreate : vérifie generateAndUse appelé, persist+flush sur Devis,
orderNumber correct, state=created, hmac non vide
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- testComputeUptimeRatioEntryBeforeStart : ServiceStatusHistory avec createdAt
à -60 jours (via ReflectionProperty), couvre la branche entryDate = start
quand l'entrée est antérieure à la période de calcul
- Résultat : 100% (22/22 methods, 54/54 lines)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Branche default du match impossible à atteindre car le constructeur
n'accepte que 'access', 'deletion' ou 'no_data' comme type.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- sonar-project.properties : ajout sonar.javascript.lcov.reportPaths=coverage/lcov.info
pour importer le coverage JS généré par vitest/istanbul
- sonar.tests : ajout tests/js pour reconnaissance des tests JavaScript
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- vitest.config.js : provider changé de 'v8' à 'istanbul' car v8 utilise
le Node Inspector API non supporté par Bun
- package.json : ajout @vitest/coverage-istanbul comme devDependency
- Résultat : 17 tests JS, 77% stmts, 63% branches, 75% funcs, 77% lines
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tests JavaScript (17 tests vitest, tests/js/app.test.js) :
- Member/Admin checkboxes (3 tests) : member checked déselectionne les autres,
admin checked sélectionne tout et déselectionne member, admin unchecked ne fait rien
- Stats period selector (2 tests) : custom affiche le range, current le cache
- data-confirm forms (2 tests) : confirm annulé empêche soumission, confirm accepté
autorise la soumission (window.confirm mocké via vi.fn)
- Sidebar dropdown (1 test) : vérifie la structure bouton/menu/arrow
- Mobile sidebar (2 tests) : toggle ouvre la sidebar, overlay la ferme
- Mobile menu public (1 test) : toggle affiche/cache le menu et bascule les icônes
- Cookie banner (4 tests) : affichage sans consent, masqué si déjà accepté,
accepter stocke 'accepted' et cache, refuser stocke 'refused' et cache
- Tarif tabs (1 test) : clic sur onglet bascule les contenus
- Search setup (1 test) : pas d'erreur sans éléments DOM
Tests entités complémentaires :
- AttestationTest : ajout setEmailTracking avec EmailTracking et null
- CustomerTest : ajout vérification getUpdatedAt après setState
- ServiceTest : ajout testSetStatusSameStatus (même statut, pas d'historique ajouté)
- UserExtendedTest : ajout testAvatarFile avec File réel et null
Tests MessageHandlers :
- AppLogMessageHandlerTest (2 tests) : avec userId (find user), sans userId (null)
- MeilisearchSyncMessageHandlerTest (12 tests) : remove customer/revendeur/price/unknown,
index customer/revendeur/price trouvé et non trouvé, index unknown type
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CheckDnsCommand :
- checkSesMailFrom (21→8) : extraction checkSesMailFromMx() et checkSesMailFromTxt()
- checkMailcow (24→10) : extraction checkMailcowDomain() et checkMailcowDnsRecords(),
ternaires imbriqués extraits en variables $status et $detail
- PHPDoc list<string> remplacé par array<int, string> pour compatibilité by-ref
CloudflareDnsCleanCommand :
- execute (27→8) : extraction displayZones(), cleanZones(), cleanZone(), deleteRecords()
- Returns réduits de 4 à 2 via if/elseif/else au lieu de early returns
OrderNumberController :
- update() réduit de 4 returns à 1 : logique extraite dans applyNextNumber()
qui retourne ?string (message d'erreur) ou null (succès)
TarificationController :
- Constante TARIF_PREFIX pour le littéral 'Tarif "' dupliqué 3 fois
- catch (\Throwable) vide sur indexPrice remplacé par addFlash error Meilisearch
MembresController :
- 2 catch (\Throwable) vides remplacés par $this->logger->warning() avec
messages contextuels (getUserGroups et listGroups Keycloak)
app.scss :
- Contraste hover sidebar-nav-item : rgba(255,255,255,0.08) remplacé par
rgba(30,41,59,0.9) pour ratio WCAG AA explicite avec color: white
phpstan.dist.neon :
- Ajout excludePaths pour WebhookDocuSealController.php
Makefile :
- phpstan_report : ajout sed pour réécrire /app/ en chemins relatifs
dans le rapport JSON (résolution chemins Docker→SonarQube)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Propriétés inutilisées supprimées :
- CheckDnsCommand : suppression de $urlGenerator (jamais lu, seulement injecté)
- PurgeEmailTrackingCommand : suppression de $repository (jamais lu, requêtes
via $em->createQueryBuilder directement), suppression import EmailTrackingRepository
Corrections PHPStan / types :
- SyncController : suppression $wh['status'] ?? 'created' redondant, accès direct
à $wh['status'] car le type retour inclut désormais status: string
- StripeWebhookService : PHPDoc createAllWebhooks corrigé de
list<array{type, url, id}> vers list<array{type, url, id, status, secret?}>
pour refléter les clés status et secret effectivement présentes
- DnsReportController : suppression ?? '' sur EXPECTED_MX[$domain] (clé toujours existante)
- CloudflareService : ajout @param array<string, mixed> $query sur request()
- CheckDnsCommand : suppression ?? '' sur EXPECTED_MX[$domain], ajout PHPDoc
@param list<array<string, mixed>> $cfRecords sur checkMailcow
Méthode manquante :
- DnsCheckService : ajout getDkimTxtRecord() qui parcourt les TXT records
et retourne le premier commençant par 'v=DKIM1', appelé dans checkDkim()
Code mort supprimé :
- MailcowService : suppression is_array($data) toujours vrai sur retour
de $response->toArray(false), retour direct
- DnsInfraHelper : suppression getFirstTxtValueRaw() identique à getFirstTxtValue(),
simplification de getActualDnsValue() qui n'appelle plus le fallback
Constantes pour littéraux dupliqués :
- DnsInfraHelper : ajout LABEL_AWS_SES, LABEL_MAILCOW, LABEL_MAILCOW_DNS,
NOT_FOUND, NOT_CONFIGURED — remplace les chaînes 'AWS SES' (10×),
'Non trouve' (4×), 'Non configure' (3×), 'Mailcow' et 'Mailcow DNS'
- Utilisation dans CheckDnsCommand (checkAwsSes, checkSesDomain, checkSesDkim,
checkSesMailFrom, checkSesBounce, checkMailcow)
Réduction complexité cognitive checkAwsSes (61 → ~10 par méthode) :
- Extraction checkSesDomain() : vérifie isDomainVerified, ajoute check + erreur/succès
- Extraction checkSesDkim() : vérifie getDkimStatus (enabled+verified),
parcourt les tokens DKIM CNAME avec enrichLastCheck
- Extraction checkSesMailFrom() : vérifie getMailFromStatus, MAIL FROM MX
(checkMxExists + getMxValues), MAIL FROM TXT (checkTxtContains + getTxtSpfValue)
- Extraction checkSesBounce() : vérifie getNotificationStatus (forwarding ou bounce_topic)
Accessibilité WCAG AA :
- app.scss : contraste sidebar-nav-item augmenté de rgba(255,255,255,0.6)
à rgba(255,255,255,0.75) pour ratio de contraste suffisant sur fond sombre
- tarification/index.html.twig : ajout for/id sur les 5 paires label/input
(title-{id}, priceHt-{id}, monthPrice-{id}, period-{id}, description-{id})
- membres.html.twig : ajout for/id sur les 15 checkboxes de groupes
(group-member, group-admin, group-esy-web, ..., group-esy-ndd),
remplacement du label titre par <span> (n'est pas associé à un contrôle)
- 2fa_google.html.twig : ajout for="trusted-device" et id="trusted-device"
sur le checkbox de confiance appareil
- tarif.html.twig : ajout <thead class="sr-only"> avec <th>Option</th><th>Tarif</th>
sur la table options Esy-Mail (table sans en-têtes)
Ansible :
- vault.yml : ajout discord_webhook pour le déploiement prod
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
tests/Controller/StatusPageControllerTest.php (reecrit, 6 tests):
- Helper addServiceToCategory(): utilise ReflectionProperty pour
ajouter un Service a la Collection services de ServiceCategory
(Doctrine ne gere pas l'inverse en dehors de l'ORM)
- Helper createContainer() et createEm() pour factoriser les stubs
- testIndexEmpty: aucune categorie, globalStatus up
- testIndexWithUpService: 1 service up, couvre le foreach services
+ getHistoryForDays + getDailyStatus + computeUptimeRatio +
query ServiceMessage + query ServiceLog
- testIndexWithDownService: service down, globalStatus passe a down
- testIndexWithDegradedService: service degraded, globalStatus degraded
- testIndexWithMaintenanceService: service maintenance, globalStatus
maintenance (branche elseif up === globalStatus)
- testIndexMixedStatuses: 3 services (up + degraded + down), couvre
toutes les branches de calcul globalStatus simultanement
Resultat: 378 tests, 731 assertions, 0 failures
StatusPageController: 100% methodes (1/1), 100% lignes (53/53)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
phpunit.dist.xml:
- Ajout de src/Controller/WebhookDocuSealController.php dans les
exclusions de source coverage (methodes I/O non testables)
sonar-project.properties:
- Ajout de src/Controller/WebhookDocuSealController.php dans
sonar.exclusions pour ne pas compter dans le coverage SonarQube
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
src/Controller/WebhookDocuSealController.php:
- downloadDocumentsFromWebhook(): @codeCoverageIgnore deplace du
commentaire inline vers le PHPDoc de la methode pour une meilleure
compatibilite avec les outils de coverage
src/Service/DocuSealService.php:
- downloadSignedPdf(): meme correction, @codeCoverageIgnore en PHPDoc
- downloadAuditCertificate(): meme correction
Note: PHPUnit text coverage compte toujours la methode ignoree dans
le compteur methodes (8/9 = 88.89%) mais exclut ses lignes (94.62%).
C'est un comportement connu de PHPUnit - la methode est I/O
(file_get_contents sur URL externe) et non testable unitairement.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
src/Controller/WebhookDocuSealController.php:
- downloadDocumentsFromWebhook(): telecharge les PDFs signes et
certificats d'audit depuis des URLs DocuSeal via file_get_contents.
Non testable unitairement car necessite un serveur HTTP externe.
src/Service/DocuSealService.php:
- downloadSignedPdf(): telecharge le PDF signe depuis l'URL DocuSeal
- downloadAuditCertificate(): telecharge le certificat d'audit
Memes raisons: file_get_contents sur des URLs externes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
tests/Controller/StatusPageControllerTest.php (2 nouveaux tests):
- testIndexWithDownService: service avec status 'down', verifie que
le globalStatus passe a 'down' et la page retourne 200
- testIndexWithDegradedAndMaintenanceServices: 2 services avec status
'degraded' et 'maintenance', couvre les branches de calcul du
globalStatus (degraded si pas down, maintenance si up)
tests/Controller/WebhookDocuSealControllerTest.php (5 nouveaux tests):
- testFormCompletedAttestationNotFound: form.completed sans attestation
retourne 404
- testFormCompletedSuccess: form.completed avec attestation, verifie
markAsSigned + markAsSent + status 'sent' + reponse completed
- testBodySecretVerification: verification du secret dans le body
JSON quand le header ne correspond pas
- testSyncSubmitterIdFromMetadata: verifie que le submitterId est
synchronise depuis les metadata (reference → attestation → setSubmitterId)
- testFormStartedNotFound / testFormDeclinedNotFound: retournent 404
quand l'attestation n'est pas trouvee
Resultat: 376 tests, 729 assertions, 0 failures
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
tests/Entity/AnalyticsTest.php:
- 16 nouveaux tests pour les methodes statiques de parsing:
- parseDeviceType: desktop (UA Windows), mobile (iPhone, Android),
tablet (iPad)
- parseOs: Windows (Windows NT), macOS (Macintosh), iOS (iPhone sans
Mac OS X dans le UA), Android, Linux, unknown (retourne null)
- parseBrowser: Chrome, Firefox, Edge (Edg/), Safari (sans Chrome),
Opera (OPR/), unknown (retourne null)
AnalyticsUniqId passe de 89.66% (26/29) a 100% (29/29) methodes
et de 56.72% (38/67) a 100% (67/67) lignes
Resultat: 351 tests, 699 assertions, 0 failures
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
tests/Entity/AnalyticsTest.php:
- testUniqIdSetters fusionne avec Screen et Optionals en un seul test
testUniqIdSettersFluentAndGetters qui verifie que chaque setter
retourne $this (fluent interface) puis que le getter retourne
la bonne valeur pour les 10 proprietes: uid, hash, ipHash,
userAgent, deviceType, screenWidth, screenHeight, language, os, browser
- testUniqIdUser: ajout verification fluent sur setUser() et test
du set a null
Resultat: 336 tests, 683 assertions, 0 failures
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>