Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: CI

# Pruebas JUnit 5 + cobertura JaCoCo (QA-06 / SCRUM-1123) con umbral bloqueante
# (jacoco:check en `mvn verify`, ver pom.xml). El análisis de SonarQube y su
# Quality Gate corren en quality-gate.yml (QA4-02 / SCRUM-1265).

on:
push:
branches: [main, develop]
pull_request:

permissions:
contents: read

jobs:
test:
name: Pruebas (Java 17)
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up JDK 17
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '17'
cache: maven

- name: Test con cobertura y umbral (JaCoCo)
run: mvn -B verify

- name: Publicar reportes de pruebas y cobertura
if: always()
uses: actions/upload-artifact@v4
with:
name: coverage-jacoco
path: |
target/site/jacoco/
target/surefire-reports/
if-no-files-found: warn
134 changes: 134 additions & 0 deletions .github/workflows/quality-gate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
name: Quality Gate

# QA4-02 (SCRUM-1265): análisis de SonarQube Community con Quality Gate
# bloqueante (ADR-0005, Políticas §14.1).
#
# El servidor SonarQube vive en la VM de Calidad (red privada 10.43.x.x), que los
# runners de GitHub no alcanzan. Por eso el trabajo se divide en dos jobs:
# 1. pruebas_cobertura: corre en ubuntu-latest. Aquí se ejecuta el código del
# repositorio (mvn verify con jacoco:check) y se generan el bytecode, las
# dependencias y el reporte jacoco.xml.
# 2. sonar_quality_gate: corre en el runner self-hosted de la VM de Calidad
# (label "calidad"). Solo descarga el artefacto y ejecuta el scanner; no
# ejecuta Maven ni código del repositorio en la VM.
#
# SonarQube Community no analiza ramas ni PRs, así que se usan dos proyectos:
# - mani-java: push a develop (estado oficial de la rama).
# - mani-java-pr: pull requests, para no pisar el estado de develop.
#
# Para que el gate BLOQUEE el merge, el ruleset de develop, release y main debe
# exigir los dos checks: "Pruebas y cobertura" y "Sonar Quality Gate". Si las
# pruebas fallan, el job de Sonar queda omitido y GitHub cuenta un job omitido
# como aprobado.
#
# Requisitos: secreto SONAR_TOKEN y variable SONAR_HOST_URL (URL del servidor en
# la VM de Calidad), definidos en el repositorio o en la organización.

on:
pull_request:
branches: [develop, release, main]
push:
branches: [develop]

permissions:
contents: read

# En PR se cancela la corrida anterior; en push a develop cada commit conserva
# su análisis.
concurrency:
group: quality-gate-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
pruebas_cobertura:
name: Pruebas y cobertura
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Configurar JDK 17
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '17'
cache: maven

- name: Pruebas con cobertura y umbral (JaCoCo)
run: mvn -B verify

# El scanner necesita las dependencias para resolver tipos: las de compilación
# para el código (sonar.java.libraries) y las de prueba para los tests
# (sonar.java.test.libraries).
- name: Copiar dependencias de compilación y de prueba
run: |
mvn -B dependency:copy-dependencies -DincludeScope=compile -DoutputDirectory=target/dependency
mvn -B dependency:copy-dependencies -DincludeScope=test -DoutputDirectory=target/test-dependency

# jacoco.xml referencia las clases por paquete y archivo, sin rutas
# absolutas, así que sirve tal cual en el workspace del runner self-hosted.
- name: Publicar compilado y cobertura
uses: actions/upload-artifact@v4
with:
name: compilado_java
path: |
target/classes
target/test-classes
target/dependency
target/test-dependency
target/site/jacoco/jacoco.xml
if-no-files-found: error
retention-days: 3

sonar_quality_gate:
name: Sonar Quality Gate
needs: pruebas_cobertura
# Los PRs desde forks no llegan al runner de la VM: su código no es de
# confianza y no tienen acceso a los secretos. En vez de omitir el job (un
# job omitido cuenta como aprobado), el job corre en ubuntu-latest y falla.
runs-on: ${{ (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) && 'ubuntu-latest' || fromJSON('["self-hosted","calidad"]') }}
steps:
- name: Bloquear PRs desde forks
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository
run: |
echo "::error::Los PRs desde forks no pasan el Quality Gate. Abra el PR desde una rama de Trama-AS."
exit 1

- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Descargar compilado y cobertura
uses: actions/download-artifact@v4
with:
name: compilado_java
path: target

# Si falta el secreto o la variable, el job falla. Un check omitido o en
# verde sin análisis daría una falsa señal de calidad.
- name: Verificar configuración de SonarQube
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_HOST_URL: ${{ vars.SONAR_HOST_URL }}
run: |
if [ -z "$SONAR_TOKEN" ]; then
echo "::error::SONAR_TOKEN no está configurado en los secretos del repositorio."
exit 1
fi
if [ -z "$SONAR_HOST_URL" ]; then
echo "::error::SONAR_HOST_URL no está configurada en las variables del repositorio."
exit 1
fi

# v8.2.2 fijada por SHA del tag (ba9859e).
- name: Análisis de SonarQube y espera del Quality Gate
uses: sonarsource/sonarqube-scan-action@ba9859eae8dd6bd29e412f25ddbbef3d032000f4
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_HOST_URL: ${{ vars.SONAR_HOST_URL }}
with:
args: >
-Dsonar.projectKey=${{ github.event_name == 'pull_request' && 'mani-java-pr' || 'mani-java' }}
-Dsonar.qualitygate.wait=true
-Dsonar.qualitygate.timeout=300
4 changes: 4 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,9 @@ Este documento sintetiza las reglas de arquitectura y patrones de código para t
- `com.trama.mani.rules.controller`: Exposición de endpoints REST.
- `com.trama.mani.rules.service`: Algoritmos de cálculo de tarifas y evaluación de condiciones.
- `com.trama.mani.rules.dto`: Objetos de transferencia de datos de entrada/salida.
- `com.trama.mani.rules.config`: Parámetros de reglas externalizados (`@ConfigurationProperties`), por tenant.
- `com.trama.mani.rules.web`: Transversales HTTP (Correlation ID, contexto de tenant, manejo uniforme de errores `{error, code, correlationId}`).
* **Rutas:** el Gateway recorta `/api/v1/rules` antes de reenviar; todo endpoint responde con y sin ese prefijo (`@RequestMapping({"/api/v1/rules", ""})`).
* **CORS:** lo maneja únicamente `MANI-APIGateway`; no usar `@CrossOrigin`.
* **Inmutabilidad y Pureza:** Los métodos de cálculo de reglas deben ser en lo posible funciones deterministas (mismas entradas generan el mismo resultado de cálculo).
* **Logging:** Propagar en los logs el encabezado `X-Correlation-ID` recibido.
55 changes: 55 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,37 @@ flowchart LR
| `GET` | `/api/v1/rules/health` | Verificación de estado del motor de reglas. | No |
| `POST` | `/api/v1/rules/evaluate-rate` | Cálculo de precio final con desglose de recargos. | Sí (JSON) |
| `POST` | `/api/v1/rules/rank-allies` | Ordenamiento y ponderación de aliados para servicio. | Opcional |
| `POST` | `/api/v1/rules/categories/evaluate` | Reglas operativas de la categoría de servicio (US-03.1.1-M6). | Sí (JSON) |

> El Gateway recorta `/api/v1/rules` antes de reenviar: cada ruta también responde sin el prefijo. Core la invoca completa por la red interna (`RULES_SERVICE_URL`, p. ej. `http://rules-service:8080`).

### Reglas de categoría (`POST /api/v1/rules/categories/evaluate`)

Reemplaza las validaciones que vivían en `crear_categoria_servicio()` (migración 003, SCRUM-857) y en Core Node. Contrato publicado en `MANI-APIGateway/docs/openapi/rules.yaml`.

**Encabezados** (los pone el Gateway tras validar el JWT, o Core al llamar internamente): `X-Tenant-Id` (obligatorio; el tenant **nunca** se toma del cuerpo), `X-User-Role` (obligatorio; `CREATE`/`UPDATE` exigen `ADMIN`), `X-Correlation-ID`.

```json
{ "categoryId": null, "context": { "operation": "CREATE", "name": " Plomería Express ", "flujoOperativo": "tarifa_estandar" } }
```

**Respuesta `200`** (la regla incumplida es `allowed=false`, no un error HTTP):
```json
{ "allowed": true, "ruleSetVersion": "category-rules-v1", "violations": [],
"normalized": { "name": "Plomería Express", "flujoOperativo": "TARIFA_ESTANDAR" },
"tenantId": "…", "categoryId": null, "operation": "CREATE", "correlationId": "…" }
```

| Operación | Reglas | Código |
| :--- | :--- | :--- |
| `CREATE` | nombre normalizado de 3 a 60 caracteres con al menos una letra | `MANI-CAT-422N` |
| `CREATE` | flujo operativo en `COTIZACION_PREVIA` / `TARIFA_ESTANDAR` | `MANI-CAT-422F` |
| `UPDATE` | las mismas, solo sobre los campos enviados | `422N` / `422F` |
| `OPERATE` | la categoría debe estar activa | `MANI-CAT-422C` |

Errores: `400 VALIDATION_ERROR | TENANT_REQUIRED`, `401 UNAUTHORIZED`, `403 FORBIDDEN | TENANT_MISMATCH`. El nombre único por tenant (`409`) sigue en el índice `ux_categoria_tenant_nombre`, porque necesita estado.

Los parámetros viven en `application.properties` (`mani.rules.category.*`) con override por tenant (`mani.rules.category.tenants[<uuid>].*`): cambiarlos solo exige redesplegar Rules. Un flujo nuevo además requiere ampliar el `CHECK ck_categoria_flujo` en BD.

### Ejemplo: Evaluación de Tarifa (`POST /api/v1/rules/evaluate-rate`)
**Petición:**
Expand Down Expand Up @@ -106,6 +137,30 @@ docker run -d -p 8080:8080 --name mani-rules mani-rules-java:local

---

## 🧪 Pruebas y cobertura

Pruebas con **JUnit 5** (vía `spring-boot-starter-test`) y cobertura con **JaCoCo**.

```bash
# Ejecutar pruebas y generar el reporte de cobertura
mvn verify
```

El reporte queda en `target/site/jacoco/` (HTML) y `target/site/jacoco/jacoco.xml`. `mvn verify` **falla** si la cobertura baja de 85 % de líneas u 80 % de ramas (`jacoco:check`, umbrales en `pom.xml`).

| Suite | Qué cubre |
| :--- | :--- |
| `CategoryRuleEvaluatorTest` | Reglas puras: límites 2/3/60/61, conteo por caracteres (emoji), letras Unicode, normalización, flujos, UPDATE parcial, OPERATE, reglas configurables, determinismo |
| `CategoryRulesPropertiesTest` | Binding de `mani.rules.category.*`, override por tenant, versión trazable, configuración incoherente que impide el arranque |
| `CategoryRulesControllerTest` | Contrato HTTP: forma de respuesta, prefijo del Gateway, roles, tenant (`TENANT_REQUIRED`, `TENANT_MISMATCH`), 400/401/403/415 |
| `UnexpectedErrorTest`, `WebSkeletonTest`, `HealthControllerSmokeTest` | 500 uniforme sin filtrar detalle, correlation ID, CORS, 404/405 |

El workflow `CI` ejecuta `mvn -B verify` en cada Pull Request y push a `main`/`develop`, publica cobertura y reportes de Surefire, y corre **SonarCloud con `sonar.qualitygate.wait=true`** (el PR falla si no pasa el Quality Gate). Ese paso se activa cuando el repositorio tiene el secret `SONAR_TOKEN` y el proyecto `Trama-AS_MANI-Java` existe en SonarCloud (DevOps); mientras tanto CI emite una advertencia en vez de fallar.

Las pruebas de contrato extremo a extremo (Gateway → Rules) viven en `MANI-APIGateway` (`postman/MANI-Rules.postman_collection.json`, job `contract-gate-rules`).

---

## 👥 Equipo y Gobernanza
* **Organización:** [TRAMA · Ingeniería de Software](https://github.com/Trama-AS)
* **Repositorio Oficial:** [MANI-Java](https://github.com/Trama-AS/MANI-Java)
59 changes: 59 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,22 @@

<properties>
<java.version>17</java.version>
<!-- SonarQube (ADR-0005): la configuración del análisis vive en
sonar-project.properties y la ejecuta .github/workflows/quality-gate.yml. -->
<!-- Umbral local de cobertura: `mvn verify` falla por debajo (jacoco:check). -->
<jacoco.minimum.line>0.85</jacoco.minimum.line>
<jacoco.minimum.branch>0.80</jacoco.minimum.branch>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
Expand All @@ -37,6 +46,56 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<!-- Cobertura: genera target/site/jacoco/jacoco.xml, la ruta que lee SonarQube por defecto. -->
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.12</version>
<executions>
<execution>
<id>prepare-agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
<execution>
<id>check</id>
<phase>verify</phase>
<goals>
<goal>check</goal>
</goals>
<configuration>
<excludes>
<exclude>com/trama/mani/rules/ManiRulesApplication.class</exclude>
</excludes>
<rules>
<rule>
<element>BUNDLE</element>
<limits>
<limit>
<counter>LINE</counter>
<value>COVEREDRATIO</value>
<minimum>${jacoco.minimum.line}</minimum>
</limit>
<limit>
<counter>BRANCH</counter>
<value>COVEREDRATIO</value>
<minimum>${jacoco.minimum.branch}</minimum>
</limit>
</limits>
</rule>
</rules>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
29 changes: 29 additions & 0 deletions sonar-project.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# =====================================================================
# Configuración de SonarQube para MANI-Java (Rules Service)
# Referencia: ADR-0005 (SAST en CI/CD con SonarQube), QA4-02 (SCRUM-1265)
# Servidor: SonarQube Community de la VM de Calidad (self-hosted, no SonarCloud)
# =====================================================================

# Identificación del proyecto. En pull requests el workflow quality-gate.yml
# sobrescribe la clave con mani-java-pr (Community no analiza ramas ni PRs).
sonar.projectKey=mani-java
sonar.projectName=MANI-Java

sonar.sourceEncoding=UTF-8
sonar.java.source=17

# Rutas de código fuente y pruebas
sonar.sources=src/main/java
sonar.tests=src/test/java

# Bytecode y dependencias (de compilación y de prueba) generados por el job
# "Pruebas y cobertura" en ubuntu-latest y descargados como artefacto: el runner
# de la VM no ejecuta Maven.
sonar.java.binaries=target/classes
sonar.java.test.binaries=target/test-classes
sonar.java.libraries=target/dependency/*.jar
sonar.java.test.libraries=target/test-dependency/*.jar

# Reporte de cobertura generado por "mvn verify" (jacoco-maven-plugin)
sonar.coverage.jacoco.xmlReportPaths=target/site/jacoco/jacoco.xml
sonar.coverage.exclusions=**/ManiRulesApplication.java
Loading
Loading