Learn-skills.dev sonarqube-quality-gate-playbook
Playbook iterativo para llevar proyectos Node y TypeScript (NestJS + React en monorepo) a cumplir Quality Gates de SonarQube sin romper build ni pipelines. Usar cuando se necesite subir cobertura priorizando New Code, eliminar issues nuevos (Bugs, Vulnerabilities, Code Smells), revisar Security Hotspots y controlar duplicacion y deuda tecnica.
git clone https://github.com/NeverSight/learn-skills.dev
T=$(mktemp -d) && git clone --depth=1 https://github.com/NeverSight/learn-skills.dev "$T" && mkdir -p ~/.claude/skills && cp -r "$T/data/skills-md/agustinalbonico/ai-customizations/sonarqube-quality-gate-playbook" ~/.claude/skills/neversight-learn-skills-dev-sonarqube-quality-gate-playbook && rm -rf "$T"
data/skills-md/agustinalbonico/ai-customizations/sonarqube-quality-gate-playbook/SKILL.mdSonarQube Quality Gate Playbook
Aplicar este flujo en iteraciones pequenas y verificables. Priorizar impacto real y bajo riesgo.
Inputs requeridos
Definir estos inputs antes del primer ciclo:
(ejemplo:SONAR_HOST_URL
)http://127.0.0.1:9000
con permisos de analisisSONAR_TOKEN
y opcionalSONAR_PROJECT_KEYSONAR_PROJECT_NAME
(default:SONAR_NEW_CODE_REFERENCE_BRANCH
)main- Rutas del monorepo:
,apps/backend
,apps/frontendpackages/* - Comandos de test por app (backend/frontend) con coverage
- Rutas de reportes
por applcov.info - Archivo Sonar (
) o parametros equivalentes en CIsonar-project.properties - Patron de exclusiones de coverage y de analisis
- Umbrales del Quality Gate (coverage, bugs, vulns, smells, duplicacion)
- Comando de build/pipeline que no se puede romper
Estrategia de priorizacion
Seguir este orden de trabajo:
- New Code primero
- Nuevos Bugs y Vulnerabilities
- Security Hotspots en estado
TO_REVIEW - Coverage en modulos criticos y tocados por el PR
- Nuevos Code Smells (priorizar severidad alta)
- Duplicacion y deuda tecnica en zonas activas
- Deuda historica (solo si no afecta el objetivo del sprint)
Heuristica de impacto
Para cada issue o archivo, calcular prioridad de manera simple:
priorityScore = (isNewCode*100) + (isBugOrVuln*80) + (isHotspotToReview*70) + severityWeight + (criticalPath*20) + (coverageGap*10) - (estimatedEffort*5)
Donde
severityWeight puede ser: blocker 40, critical 30, major 20, minor 10.
Regla anti inflado de coverage
No aceptar tests que solo suben porcentaje sin validar comportamiento:
- Todo test nuevo debe cubrir caso feliz, caso negativo y borde
- Prohibido snapshot-only como unica asercion
- Evitar tests triviales de codigo sin logica
- Si el test no falla ante un bug real, no cuenta como cobertura de valor
Pipeline del skill (paso a paso)
Runbook de discrepancia de cobertura (caso reutilizable)
Usar este runbook cuando SonarQube muestre cobertura mucho menor a la local.
Sintoma tipico
- Local (Jest/Vitest): cobertura alta (por ejemplo >90%)
- SonarQube: cobertura sensiblemente menor (por ejemplo 40-60%)
Causas raiz frecuentes
- Rutas
con backslashes (SF:
) en\\
generadas en Windows.lcov.info - SonarQube ejecutando en Linux/containers sin poder mapear rutas con
.\\ - Packages compartidos con cobertura baja (ejemplo
) tirando abajo el global.packages/utils
Diagnostico rapido
- Revisar primeras entradas
delSF:
de cada app/package.lcov.info - Verificar en logs de
si hay archivos de coverage no mapeados o ignorados.sonar-scanner - Comparar cobertura por modulo (backend/frontend/packages) para detectar outliers.
Comandos sugeridos:
Get-Content "apps\\backend\\coverage\\lcov.info" | Select-String "^SF:" | Select-Object -First 10 Get-Content "apps\\frontend\\coverage\\lcov.info" | Select-String "^SF:" | Select-Object -First 10
Si aparecen rutas como
SF:src\\app.ts, normalizar antes del scan.
Remediacion recomendada
- Normalizar rutas a forward slash (
) en todos los/
antes delcov.info
.sonar-scanner - Re-ejecutar tests de coverage y scan.
- Subir cobertura del paquete/modulo de menor porcentaje para no sesgar el global.
Ejemplo portable (
scripts/fix-lcov-paths.js):
const fs = require('node:fs'); const reports = [ 'apps/backend/coverage/lcov.info', 'apps/frontend/coverage/lcov.info', 'packages/utils/coverage/lcov.info' ]; for (const reportPath of reports) { if (!fs.existsSync(reportPath)) continue; const content = fs.readFileSync(reportPath, 'utf8'); const normalized = content.replace(/\\\\/g, '/'); fs.writeFileSync(reportPath, normalized); }
Guardrail CI (fallar si quedan backslashes):
if grep -q 'SF:.*\\\\' apps/backend/coverage/lcov.info; then echo "ERROR: Backslashes encontrados en lcov.info" exit 1 fi
Paso 1: Detectar gap actual
- Ejecutar tests con coverage en backend/frontend/packages relevantes
- Ejecutar analisis SonarQube
- Levantar metricas e issues nuevos para priorizar
Comandos genericos:
bun run --cwd apps/backend test -- --coverage bun run --cwd apps/frontend test -- --coverage sonar-scanner ` -Dsonar.host.url=$env:SONAR_HOST_URL ` -Dsonar.token=$env:SONAR_TOKEN ` -Dsonar.projectKey=$env:SONAR_PROJECT_KEY ` -Dsonar.qualitygate.wait=true curl -4 -s "$env:SONAR_HOST_URL/api/measures/component?component=$env:SONAR_PROJECT_KEY&metricKeys=coverage,new_coverage,bugs,new_bugs,vulnerabilities,new_vulnerabilities,code_smells,new_code_smells,duplicated_lines_density,new_duplicated_lines_density" -u "$env:SONAR_TOKEN:" curl -4 -s "$env:SONAR_HOST_URL/api/issues/search?componentKeys=$env:SONAR_PROJECT_KEY&resolved=false&inNewCodePeriod=true&types=BUG,VULNERABILITY,CODE_SMELL&ps=500" -u "$env:SONAR_TOKEN:" curl -4 -s "$env:SONAR_HOST_URL/api/hotspots/search?projectKey=$env:SONAR_PROJECT_KEY&status=TO_REVIEW&ps=500" -u "$env:SONAR_TOKEN:"
Paso 2: Generar o actualizar config Sonar
Crear o actualizar
sonar-project.properties en raiz con rutas reales del monorepo.
Paso 3: Asegurar lectura de coverage real
- Verificar existencia de
lcov.info - Configurar
con todas las rutassonar.javascript.lcov.reportPaths - Validar en logs del scanner que no haya warnings de coverage faltante
- Verificar que lineas
usenSF:
y no/\\ - Si hay entorno mixto Windows/Linux, correr normalizacion de paths antes del scan
- Confirmar que packages compartidos relevantes tambien reporten coverage (no solo apps)
Paso 4: Ejecutar tests de forma consistente
- Usar los mismos comandos y flags en local y CI
- Publicar artefactos de coverage
- No mezclar runners/flags entre entornos
Paso 5: Iterar por lotes pequenos
- Lote A: Bugs/Vulns/Hotspots nuevos
- Lote B: Coverage en New Code
- Lote C: Code Smells nuevos
- Lote D: Duplicacion/deuda en areas tocadas
Generacion de tests guiada (backend y frontend)
1) Identificar archivos sin cobertura
- Fuente principal: SonarQube (archivos con uncovered lines en New Code)
- Fuente secundaria:
+ archivos modificados del PRlcov.info
git diff --name-only origin/main...HEAD | Where-Object { $_ -match '\.(ts|tsx)$' }
2) Elegir tipo de test correcto
- Unit: logica pura y funciones con pocas dependencias
- Integration (backend): controladores + servicios + repositorios mockeados
- Component (frontend): interacciones, estados y accesibilidad
- E2E: solo flujos criticos, no como reemplazo de unit/component
3) Mocking strategy
- DB: fake repo o base de test aislada
- HTTP externo: mocks deterministas (sin pegar a servicios reales)
- Tiempo/random: controlar reloj y valores aleatorios
- Evitar mocks excesivos que oculten defectos reales
4) Criterios minimos de calidad del test
- Estructura Arrange/Act/Assert clara
- Aserciones semanticas del comportamiento esperado
- Caso feliz + negativo + borde obligatorios
- Test estable y sin flakiness
- Nombre orientado a comportamiento
5) Naming y estructura
- Backend:
*.spec.ts - Frontend:
*.test.tsx - Patron recomendado:
should <resultado> when <condicion>returns <error> when <input invalido>
Remediacion de Code Smells, Bugs y Security
Aplicar fixes quirurgicos, sin cambios de estilo masivos.
Checklist por categoria:
- Complejidad: extraer funciones, usar early return, bajar anidacion
- Duplicacion: consolidar utilidades y evitar copy-paste
- Nullability: guards explicitos y defaults seguros
- Manejo de errores: evitar
vacio, propagar con contextocatch - Leaks/resources: cerrar timers, subscripciones, conexiones
- Regex/DoS: evitar patrones catastróficos y limitar input
- Dependencias inseguras: actualizar librerias vulnerables
- Sanitizacion: validar y sanitizar input, evitar XSS/inyeccion
- Security Hotspots: revisar y documentar resolucion por cada item
Reglas de exclusion y trade-offs
Exclusiones aceptables (con justificativo):
sin logica**/index.ts- DTOs o types simples sin ramas
- codigo generado (
)**/generated/** - bootstrap minimo sin logica de negocio
Exclusiones no aceptables:
- servicios/casos de uso/controladores con logica
- hooks y validadores con ramas
- codigo de autenticacion/autorizacion
- excluir para pasar el porcentaje sin mejorar calidad
Regla: cada exclusion debe registrar el motivo tecnico.
Definicion de Done y metricas
Declarar ciclo completado solo con evidencia:
- Quality Gate en estado
PASSED new_coverage >= 80new_bugs = 0new_vulnerabilities = 0new_code_smells = 0new_security_hotspots_reviewed = 100%- Duplicacion y deuda dentro de umbral del gate
- Build y pipelines verdes
Evidencia minima a adjuntar:
- salida de
consonar-scannersonar.qualitygate.wait=true - salida de APIs de metricas/issues/hotspots
- cobertura por carpeta (backend/frontend/packages)
- resumen de issues resueltos (antes/despues)
Snippets de configuracion
sonar-project.properties
sonar.projectKey=my-org_my-monorepo sonar.projectName=my-monorepo sonar.sourceEncoding=UTF-8 sonar.sources=apps/backend/src,apps/frontend/src,packages sonar.tests=apps/backend,apps/frontend,packages sonar.test.inclusions=**/*.spec.ts,**/*.test.ts,**/*.test.tsx,**/*.spec.tsx sonar.javascript.lcov.reportPaths=apps/backend/coverage/lcov.info,apps/frontend/coverage/lcov.info sonar.newCode.referenceBranch=main sonar.exclusions=**/dist/**,**/build/**,**/node_modules/**,**/*.d.ts sonar.coverage.exclusions=**/index.ts,**/*.dto.ts,**/generated/**,**/*.stories.tsx
Jest backend (ejemplo)
import type { Config } from 'jest'; const config: Config = { preset: 'ts-jest', testEnvironment: 'node', collectCoverage: true, coverageDirectory: 'coverage', coverageReporters: ['text', 'lcov', 'html'], collectCoverageFrom: [ 'src/**/*.{ts,tsx}', '!src/**/*.dto.ts', '!src/**/index.ts', '!src/**/generated/**' ] }; export default config;
Vitest frontend (ejemplo)
import { defineConfig } from 'vitest/config'; export default defineConfig({ test: { environment: 'jsdom', coverage: { provider: 'v8', reporter: ['text', 'lcov', 'html'], reportsDirectory: './coverage', include: ['src/**/*.{ts,tsx}'], exclude: ['src/**/*.stories.tsx', 'src/**/index.ts'] } } });
GitHub Actions (ejemplo)
name: quality-gate on: pull_request: push: branches: [main] jobs: quality: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - uses: oven-sh/setup-bun@v2 - run: bun install --frozen-lockfile - name: Backend tests with coverage run: bun run --cwd apps/backend test -- --coverage - name: Frontend tests with coverage run: bun run --cwd apps/frontend test -- --coverage - name: Normalize lcov paths (Windows/Linux safe) run: node scripts/fix-lcov-paths.js - name: Validate lcov path format run: | if grep -q 'SF:.*\\\\' apps/backend/coverage/lcov.info; then echo "ERROR: Backslashes encontrados en backend lcov.info" exit 1 fi - name: Build run: bun run build - name: SonarQube Scan env: SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} run: | sonar-scanner \ -Dsonar.host.url=$SONAR_HOST_URL \ -Dsonar.token=$SONAR_TOKEN \ -Dsonar.qualitygate.wait=true
GitLab CI (ejemplo)
stages: - test - quality variables: GIT_DEPTH: "0" test_and_build: stage: test image: oven/bun:1 script: - bun install --frozen-lockfile - bun run --cwd apps/backend test -- --coverage - bun run --cwd apps/frontend test -- --coverage - node scripts/fix-lcov-paths.js - bun run build artifacts: when: always paths: - apps/backend/coverage/ - apps/frontend/coverage/ sonarqube: stage: quality image: sonarsource/sonar-scanner-cli:latest dependencies: - test_and_build script: - sonar-scanner -Dsonar.host.url=$SONAR_HOST_URL -Dsonar.token=$SONAR_TOKEN -Dsonar.qualitygate.wait=true allow_failure: false
README del skill
Usar este resumen rapido en ejecucion:
- correr tests con coverage
- correr sonar-scanner con wait del gate
- priorizar New Code -> Bugs/Vulns -> Hotspots -> Coverage -> Smells
- iterar en lotes pequenos y adjuntar evidencia
Comandos base:
bun run --cwd apps/backend test -- --coveragebun run --cwd apps/frontend test -- --coveragesonar-scanner -Dsonar.host.url=$SONAR_HOST_URL -Dsonar.token=$SONAR_TOKEN -Dsonar.qualitygate.wait=true
Checklist de PR
- No hay nuevos Bugs ni Vulnerabilities
- No hay nuevos Code Smells
- New Coverage >= 80%
- Hotspots nuevos revisados
- Sin exclusiones injustificadas
- Tests con caso feliz, negativo y borde
- Build y CI en verde
- Evidencia de Sonar adjunta
-
sin backslashes en lineaslcov.infoSF: - Packages compartidos sin brecha fuerte de coverage
Plantillas de comandos listas para pegar
$env:SONAR_HOST_URL="http://127.0.0.1:9000" $env:SONAR_TOKEN="<token>" $env:SONAR_PROJECT_KEY="<project-key>" bun run --cwd apps/backend test -- --coverage bun run --cwd apps/frontend test -- --coverage bun run build sonar-scanner ` -Dsonar.host.url=$env:SONAR_HOST_URL ` -Dsonar.token=$env:SONAR_TOKEN ` -Dsonar.projectKey=$env:SONAR_PROJECT_KEY ` -Dsonar.qualitygate.wait=true curl -4 -s "$env:SONAR_HOST_URL/api/measures/component?component=$env:SONAR_PROJECT_KEY&metricKeys=new_coverage,new_bugs,new_vulnerabilities,new_code_smells,new_duplicated_lines_density" -u "$env:SONAR_TOKEN:"
Defaults a ajustar si hay incertidumbre
- branch de New Code:
main - umbral New Coverage:
80 - duplicacion maxima new code:
3% - coverage exclusions: solo archivos sin logica
- estrategia de lotes: 1 PR por categoria critica