NutriSync — Servidor Mock HTTP con WireMock

Simulación de APIs externas · HealthLab · PagosRapidos · WhatsApp Business

● Running :8080 WireMock 3.5.4 Docker
11
Stubs activos
3
APIs simuladas
4
Escenarios de fallo
1
Escenario stateful
🐳
Setup — Docker Standalone setup-wiremock.sh
setup-wiremock.sh
#!/bin/bash
# NutriSync — Inicia WireMock para simular HealthLab, PagosRapidos y WhatsApp APIs

docker run -d --name nutrisync-wiremock \
  -p 8080:8080 \
  -v $(pwd)/wiremock:/home/wiremock \
  --restart unless-stopped \
  wiremock/wiremock:3.5.4 \
    --global-response-templating \
    --verbose

# Verificar que arrancó correctamente
echo "Esperando WireMock..."
until curl -s http://localhost:8080/__admin/health > /dev/null; do
  sleep 1
done
echo "✅ WireMock activo en http://localhost:8080"
echo "📊 Admin UI: http://localhost:8080/__admin/mappings"
📋
Stubs Configurados wiremock/mappings/
🔬 HealthLab API api.healthlab.io
  • GET /v2/patient/{id}/results 200
  • GET /v2/patient/{id}/history 200
  • POST /v2/order/bloodtest 201
  • GET /v2/patient/{id}/results 503 FAULT
healthlab-results.json
{
  "request": {
    "method": "GET",
    "urlPathPattern": "/v2/patient/[0-9]+/results",
    "headers": {
      "X-Api-Key": { "present": true }
    }
  },
  "response": {
    "status": 200,
    "headers": {
      "Content-Type": "application/json"
    },
    "jsonBody": {
      "patientId": "{{request.pathSegments.[2]}}",
      "analysisDate": "2026-06-10",
      "status": "completed",
      "markers": [
        { "code": "GLU", "value": 94,
          "unit": "mg/dL", "range": "70-100" },
        { "code": "HBA1C", "value": 5.4,
          "unit": "%", "range": "<5.7" }
      ]
    },
    "transformers": ["response-template"]
  }
}
💳 PagosRapidos API api.pagosrapidos.es
  • POST /v1/subscription/create 201
  • GET /v1/subscription/{id} 200
  • PUT /v1/subscription/{id}/cancel 200
  • POST /v1/subscription/create 503 FAULT
pagos-subscription-create.json
{
  "request": {
    "method": "POST",
    "urlPath": "/v1/subscription/create",
    "bodyPatterns": [
      { "matchesJsonPath": "$.clinicId" },
      { "matchesJsonPath": "$.plan" },
      { "matchesJsonPath": "$.paymentMethod" }
    ]
  },
  "response": {
    "status": 201,
    "jsonBody": {
      "subscriptionId": "{{randomValue type='UUID'}}",
      "clinicId": "{{jsonPath request.body '$.clinicId'}}",
      "plan": "{{jsonPath request.body '$.plan'}}",
      "status": "trial",
      "trialEndsAt": "2026-07-16T00:00:00Z"
    },
    "transformers": ["response-template"]
  }
}
💬 WhatsApp Business API api.whatsapp.com/v1
  • POST /messages 200
  • GET /messages/{id}/status 200
  • POST /messages (template) 200
whatsapp-send-message.json
{
  "request": {
    "method": "POST",
    "urlPath": "/messages",
    "headers": {
      "Authorization": {
        "matches": "Bearer .+"
      }
    },
    "bodyPatterns": [
      { "matchesJsonPath": "$.to" },
      { "matchesJsonPath": "$.type" }
    ]
  },
  "response": {
    "status": 200,
    "jsonBody": {
      "messages": [{
        "id": "wamid.{{randomValue type='UUID'}}",
        "to": "{{jsonPath request.body '$.to'}}",
        "status": "sent"
      }]
    },
    "transformers": ["response-template"]
  }
}
🔄
Escenario Stateful — Ciclo de Vida de Suscripción subscription-lifecycle.json
Started
Estado inicial
POST /v1/subscription/create
Trial
status: trial
14 días gratis
GET /v1/subscription/{id}
Active
status: active
pago procesado
GET /v1/subscription/{id}
Cancelled
status: cancelled
acceso revocado
PUT /v1/subscription/{id}/cancel
subscription-lifecycle.json (extracto de escenario stateful)
{
  "mappings": [
    {
      "scenarioName": "Subscription Lifecycle",
      "requiredScenarioState": "Started",
      "newScenarioState": "Trial",
      "request": { "method": "POST", "urlPath": "/v1/subscription/create" },
      "response": {
        "status": 201,
        "jsonBody": { "id": "sub-ns-001", "status": "trial", "daysLeft": 14 }
      }
    },
    {
      "scenarioName": "Subscription Lifecycle",
      "requiredScenarioState": "Trial",
      "newScenarioState": "Active",
      "request": { "method": "GET", "urlPath": "/v1/subscription/sub-ns-001" },
      "response": {
        "status": 200,
        "jsonBody": { "id": "sub-ns-001", "status": "active", "plan": "pro" }
      }
    },
    {
      "scenarioName": "Subscription Lifecycle",
      "requiredScenarioState": "Active",
      "newScenarioState": "Cancelled",
      "request": { "method": "PUT", "urlPath": "/v1/subscription/sub-ns-001/cancel" },
      "response": {
        "status": 200,
        "jsonBody": { "id": "sub-ns-001", "status": "cancelled" }
      }
    }
  ]
}
Fault Injection — Pruebas de Resiliencia fault-injection.json
Endpoint Tipo de Fallo Configuración Qué prueba
GET /v2/patient/{id}/results SLOW 8s fixedDelayMilliseconds: 8000 Circuit breaker HealthLab
POST /v1/subscription/create 503 Service status: 503, body: {error: "gateway_timeout"} Retry logic en pagos
POST /messages CONN RESET fault: "CONNECTION_RESET_BY_PEER" Cola de notificaciones WA
GET /v2/patient/*/results RANDOM DELAY lognormal median: 500ms, sigma: 0.6 Timeouts variables lab
fault-injection.json (HealthLab slow + WhatsApp connection reset)
{
  "mappings": [
    {
      "name": "healthlab-slow-response",
      "request": {
        "method": "GET",
        "urlPathPattern": "/v2/patient/slow/results"
      },
      "response": {
        "status": 200,
        "fixedDelayMilliseconds": 8000,
        "jsonBody": { "data": "delayed response" }
      }
    },
    {
      "name": "whatsapp-connection-reset",
      "request": {
        "method": "POST",
        "urlPath": "/messages/fail"
      },
      "response": {
        "fault": "CONNECTION_RESET_BY_PEER"
      }
    },
    {
      "name": "pagos-503",
      "request": {
        "method": "POST",
        "urlPath": "/v1/subscription/create",
        "headers": {
          "X-Force-Error": { "equalTo": "503" }
        }
      },
      "response": {
        "status": 503,
        "jsonBody": {
          "error": "gateway_timeout",
          "retryAfter": 30
        }
      }
    }
  ]
}
Tip: Activa los stubs de fallo solo en tests de resiliencia usando la cabecera X-Force-Error: 503 para no interferir con los tests de happy-path.
⚙️
GitHub Actions CI — Integration Tests .github/workflows/integration.yml
📦
Checkout + Setup
Node 20, npm ci
~20s
🔌
WireMock Service
Docker :8080 listo
~8s
Load Stubs
copy wiremock/ mappings
~2s
🧪
Jest Tests
MOCK_API_URL=:8080
~45s
Report
JUnit XML + coverage
~3s
.github/workflows/integration.yml
name: NutriSync Integration Tests
on:
  push:
    branches: [develop, main]
  pull_request:
    branches: [main]

jobs:
  integration-tests:
    runs-on: ubuntu-latest
    services:
      wiremock:
        image: wiremock/wiremock:3.5.4
        ports:
          - 8080:8080
        options: >-
          --health-cmd "curl -f http://localhost:8080/__admin/health"
          --health-interval 5s
          --health-timeout 3s
          --health-retries 5
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 20 }
      - name: Install dependencies
        run: npm ci
      - name: Load WireMock stubs
        run: |
          cp -r wiremock/mappings /tmp/wiremock-stubs
          curl -X POST http://localhost:8080/__admin/mappings/import \
            -H 'Content-Type: application/json' \
            --data-binary @wiremock/mappings/all-stubs.json
      - name: Run integration tests
        run: npm run test:integration
        env:
          HEALTHLAB_API_URL: http://localhost:8080
          PAGOS_API_URL: http://localhost:8080
          WHATSAPP_API_URL: http://localhost:8080
          NODE_ENV: test
📁
Estructura de Archivos
📂 Árbol del proyecto NutriSync
nutrisync-backend/ ├── wiremock/ │ ├── mappings/ │ │ ├── healthlab-results.json # GET patient results │ │ ├── healthlab-history.json # GET patient history │ │ ├── healthlab-order.json # POST blood test order │ │ ├── pagos-subscription-create.json │ │ ├── pagos-subscription-get.json │ │ ├── pagos-subscription-cancel.json │ │ ├── whatsapp-send.json │ │ ├── whatsapp-status.json │ │ ├── whatsapp-template.json │ │ ├── subscription-lifecycle.json # Stateful scenario │ │ └── fault-injection.json # Resilience tests │ └── __files/ # Static response bodies ├── .github/workflows/ │ └── integration.yml ├── setup-wiremock.sh # Dev local quick start └── src/tests/integration/ ├── healthlab.test.ts ├── pagos.test.ts └── whatsapp.test.ts
🧪 Test de integración de ejemplo (Jest)
src/tests/integration/healthlab.test.ts
import { getPatientResults } from '../../services/healthlab'

const MOCK_URL = process.env.HEALTHLAB_API_URL
  ?? 'http://localhost:8080'

describe('HealthLab — Patient Results', () => {

  it('retorna marcadores de análisis correctamente', async () => {
    const result = await getPatientResults(
      '42', { baseUrl: MOCK_URL }
    )
    expect(result.patientId).toBe('42')
    expect(result.markers).toHaveLength(2)
    expect(result.markers[0].code).toBe('GLU')
  })

  it('activa circuit breaker tras 8s timeout', async () => {
    await expect(
      getPatientResults('slow', {
        baseUrl: MOCK_URL,
        timeout: 3000
      })
    ).rejects.toThrow('ETIMEDOUT')
  })

})
Resultado esperado: Tests corren en ~50s en CI sin acceso a la red externa. Sin coste de API. Sin datos de pacientes reales (RGPD compliant).