sonarqube-quality-gate-playbook
SonarQube 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:
SONAR_HOST_URL(ejemplo:http://127.0.0.1:9000)SONAR_TOKENcon permisos de analisisSONAR_PROJECT_KEYy opcionalSONAR_PROJECT_NAMESONAR_NEW_CODE_REFERENCE_BRANCH(default:main)- Rutas del monorepo:
apps/backend,apps/frontend,packages/* - Comandos de test por app (backend/frontend) con coverage
- Rutas de reportes
lcov.infopor app - Archivo Sonar (
sonar-project.properties) o parametros equivalentes en CI - 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
SF:con backslashes (\\) enlcov.infogeneradas en Windows. - SonarQube ejecutando en Linux/containers sin poder mapear rutas con
\\. - Packages compartidos con cobertura baja (ejemplo
packages/utils) tirando abajo el global.
Diagnostico rapido
- Revisar primeras entradas
SF:dellcov.infode cada app/package. - Verificar en logs de
sonar-scannersi hay archivos de coverage no mapeados o ignorados. - 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 loslcov.infoantes desonar-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
sonar.javascript.lcov.reportPathscon todas las rutas - Validar en logs del scanner que no haya warnings de coverage faltante
- Verificar que lineas
SF:usen/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:
lcov.info+ archivos modificados del PR
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
catchvacio, propagar con contexto - 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):
**/index.tssin logica- 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
sonar-scannerconsonar.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
-
lcov.infosin backslashes en lineasSF: - 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