Appearance
Testing Backend - Portal API
Estado: Planificado
Estrategia de Testing
Piramide de Tests
- Unit Tests (Muchos): >80% coverage
- Integration Tests (Moderados): Tests con DB real
- E2E Tests (Pocos): Flujos completos
Coverage objetivo: > 80%
Ubicacion de Tests
Tests en la estructura DDD del modulo:
Modules/Portal/Tests/
├── Unit/
│ ├── Auth/
│ │ ├── LoginServiceTest.php
│ │ ├── RegisterServiceTest.php
│ │ ├── ForgotPasswordServiceTest.php
│ │ └── ResetPasswordServiceTest.php
│ ├── Payment/
│ │ ├── PaymentGatewayFactoryTest.php
│ │ ├── MercadoPagoAdapterTest.php
│ │ └── PaymentServiceTest.php
│ └── CtaCte/
│ └── PortalCtaCteServiceTest.php
└── Integration/
├── Auth/
│ ├── LoginFlowTest.php
│ └── RegisterFlowTest.php
└── Payment/
├── PaymentFlowTest.php
└── WebhookProcessingTest.phpHerramientas
- PHPUnit
- Mockery para mocks
- Faker para datos de prueba
Unit Tests
Auth: LoginService
php
class LoginServiceTest extends TestCase
{
public function testLoginConCredencialesValidas(): void
{
// Dado un cliente registrado con DNI y password
// Cuando llama a login con credenciales correctas
// Entonces retorna JWT token + datos del cliente
}
public function testLoginConPasswordIncorrecto(): void
{
// Dado un cliente registrado
// Cuando llama a login con password incorrecto
// Entonces lanza excepcion de credenciales invalidas
}
public function testLoginConDniNoRegistrado(): void
{
// Dado un DNI que no tiene cuenta en portal
// Cuando llama a login
// Entonces lanza excepcion de cliente no encontrado
}
}Auth: RegisterService
php
class RegisterServiceTest extends TestCase
{
public function testRegistroExitosoConDniEnOrdcon(): void
{
// Dado un DNI que existe en ordcon pero no tiene cuenta portal
// Cuando llama a register con DNI + email + password
// Entonces crea cuenta portal y retorna JWT
}
public function testRegistroFallaConDniNoEnOrdcon(): void
{
// Dado un DNI que NO existe en ordcon
// Cuando llama a register
// Entonces lanza excepcion "DNI no encontrado en el sistema"
}
public function testRegistroFallaConDniYaRegistrado(): void
{
// Dado un DNI que ya tiene cuenta portal
// Cuando llama a register
// Entonces lanza excepcion "DNI ya registrado"
}
public function testRegistroValidaFormatoEmail(): void
{
// Dado un email con formato invalido
// Cuando llama a register
// Entonces lanza excepcion de validacion
}
}Auth: ForgotPasswordService
php
class ForgotPasswordServiceTest extends TestCase
{
public function testGeneraCodigoYEnviaEmail(): void
{
// Dado un email asociado a una cuenta portal
// Cuando llama a forgot-password
// Entonces genera codigo, lo guarda y envia email
}
public function testEmailNoRegistradoNoRevelaInformacion(): void
{
// Dado un email que no existe en el sistema
// Cuando llama a forgot-password
// Entonces retorna OK (no revela si el email existe o no)
}
}Auth: ResetPasswordService
php
class ResetPasswordServiceTest extends TestCase
{
public function testResetConCodigoValido(): void
{
// Dado un codigo de reset valido y no expirado
// Cuando llama a reset-password con codigo + nueva password
// Entonces actualiza password y retorna OK
}
public function testResetConCodigoExpirado(): void
{
// Dado un codigo de reset expirado
// Cuando llama a reset-password
// Entonces lanza excepcion "codigo expirado"
}
public function testResetConCodigoInvalido(): void
{
// Dado un codigo de reset que no existe
// Cuando llama a reset-password
// Entonces lanza excepcion "codigo invalido"
}
}Payment: PaymentGatewayFactory
php
class PaymentGatewayFactoryTest extends TestCase
{
public function testCreaAdapterMercadoPago(): void
{
// Dado config con gateway = "mercadopago"
// Cuando llama a factory::create
// Entonces retorna instancia de MercadoPagoAdapter
}
public function testLanzaExcepcionParaGatewayDesconocido(): void
{
// Dado config con gateway = "desconocido"
// Cuando llama a factory::create
// Entonces lanza InvalidArgumentException
}
}Payment: MercadoPagoAdapter
php
class MercadoPagoAdapterTest extends TestCase
{
public function testCreatePaymentRetornaRedirectUrl(): void
{
// Dado datos de pago validos
// Cuando llama a createPayment
// Entonces retorna external_id y redirect_url
}
public function testValidateWebhookConFirmaValida(): void
{
// Dado headers y body con firma HMAC valida
// Cuando llama a validateWebhook
// Entonces retorna true
}
public function testValidateWebhookConFirmaInvalida(): void
{
// Dado headers con firma HMAC invalida
// Cuando llama a validateWebhook
// Entonces retorna false
}
}Payment: PaymentService (Creacion Automatica de Recibo)
php
class PaymentServiceTest extends TestCase
{
public function testWebhookAprobadoCreaReciboAutomatico(): void
{
// Dado un pago pendiente en portal_payments
// Cuando el webhook reporta status = approved
// Entonces crea recibo en CtaCte y actualiza portal_payments con recibo_id
}
public function testWebhookAprobadoIdempotente(): void
{
// Dado un pago ya procesado (status = approved, recibo_id != null)
// Cuando llega un segundo webhook con el mismo external_id
// Entonces no crea recibo duplicado, retorna OK
}
public function testWebhookRechazadoNoCreanRecibo(): void
{
// Dado un pago pendiente
// Cuando el webhook reporta status = rejected
// Entonces actualiza status pero NO crea recibo
}
public function testWebhookConFirmaInvalidaEsRechazado(): void
{
// Dado headers con firma HMAC invalida
// Cuando llega un webhook
// Entonces lanza excepcion y no procesa el pago
}
}CtaCte: PortalCtaCteService
php
class PortalCtaCteServiceTest extends TestCase
{
public function testObtenerDeudasDelCliente(): void
{
// Dado un cliente con facturas pendientes
// Cuando llama a obtener deudas
// Entonces retorna lista de facturas con monto, vencimiento, estado
}
public function testResumenDeCuenta(): void
{
// Dado un cliente con facturas y pagos
// Cuando llama a resumen de cuenta
// Entonces retorna saldo total, facturas vencidas, ultimo pago
}
}Integration Tests
Setup
- Docker PostgreSQL para tests
- Migraciones automaticas del modulo Portal
- Datos de prueba (fixtures) con clientes en ordcon
- Cleanup automatico entre tests
Auth: LoginFlow
php
class LoginFlowTest extends TestCase
{
public function testFlujoCompletoDeLogin(): void
{
// 1. Crear cliente en ordcon (fixture)
// 2. Registrar cuenta portal
// 3. Login con DNI + password
// 4. Verificar JWT valido
// 5. Acceder a endpoint protegido con JWT
}
}Auth: RegisterFlow
php
class RegisterFlowTest extends TestCase
{
public function testFlujoCompletoDeRegistro(): void
{
// 1. Crear cliente en ordcon (fixture)
// 2. POST /portal/auth/register con DNI + email + password
// 3. Verificar que se creo portal_client
// 4. Verificar que retorna JWT valido
}
public function testRegistroConDniNoEnOrdcon(): void
{
// 1. POST /portal/auth/register con DNI inexistente en ordcon
// 2. Verificar respuesta 422 con mensaje de error
}
}Payment: PaymentFlow
php
class PaymentFlowTest extends TestCase
{
public function testFlujoIniciarPago(): void
{
// 1. Crear cliente con deudas (fixtures)
// 2. POST /portal/pagos/iniciar con facturas seleccionadas
// 3. Verificar que se creo portal_payment con status pending
// 4. Verificar que retorna redirect_url
}
}Payment: WebhookProcessing
php
class WebhookProcessingTest extends TestCase
{
public function testWebhookAprobadoCreaRecibo(): void
{
// 1. Crear portal_payment pendiente (fixture)
// 2. POST /portal/pagos/webhook con payload de aprobacion
// 3. Verificar que portal_payment tiene status approved
// 4. Verificar que se creo recibo en CtaCte
// 5. Verificar que portal_payment tiene recibo_id
}
public function testWebhookDuplicadoNoCreaReciboDuplicado(): void
{
// 1. Crear portal_payment ya aprobado con recibo (fixture)
// 2. POST /portal/pagos/webhook con mismo external_id
// 3. Verificar que no se creo segundo recibo
// 4. Verificar respuesta 200 OK (idempotente)
}
}Comandos
Ejecutar tests del modulo Portal:
bash
vendor/bin/phpunit Modules/Portal/Tests/Unit/
vendor/bin/phpunit Modules/Portal/Tests/Integration/Ejecutar todos los tests del modulo:
bash
vendor/bin/phpunit Modules/Portal/Tests/Coverage:
bash
vendor/bin/phpunit --coverage-html coverage/ Modules/Portal/Tests/Mejores Practicas
- Usar mocks para dependencias externas (MercadoPago API, email service)
- Un assert por test cuando sea posible
- Nombres descriptivos de tests (en espanol, describiendo el escenario)
- Setup y teardown apropiados
- Tests independientes entre si
- Fixtures compartidos para datos de ordcon (clientes de prueba)
- Verificar idempotencia en todos los flujos de webhook
Componentes NO testeados por el Portal
Los cupones de pago reutilizan el servicio existente CuponPagoService. Los tests de cupones ya existen en la suite del servicio original. No se duplican tests en el modulo Portal.