From a86e4f12b1c375ea295bf03d81c0bcb264a0cd54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Labrado=20Rodr=C3=ADguez?= Date: Sat, 6 Dec 2025 11:27:39 +0100 Subject: [PATCH] Se implementa prueba tecnica para generar api y operaciones de conexion en cajeros automaticos --- pom.xml | 4 + .../demo/controller/CajeroController.java | 63 +++++ .../demo/controller/CuentaController.java | 23 ++ .../es/nextdigital/demo/model/Cuenta.java | 32 +++ .../es/nextdigital/demo/model/Movimiento.java | 43 ++++ .../es/nextdigital/demo/model/Tarjeta.java | 63 +++++ .../demo/model/TipoMovimiento .java | 9 + .../nextdigital/demo/model/TipoTarjeta.java | 6 + .../demo/repository/CuentaRepository.java | 26 ++ .../demo/repository/TarjetaRepository.java | 21 ++ .../demo/service/CajeroService.java | 228 ++++++++++++++++++ .../demo/service/CuentaService.java | 28 +++ .../nextdigital/demo/util/IbanValidator.java | 11 + .../CajeroControllerIntegrationTest.java | 47 ++++ .../demo/service/CajeroServiceTest.java | 50 ++++ .../demo/util/IbanValidatorTest.java | 23 ++ 16 files changed, 677 insertions(+) create mode 100644 src/main/java/es/nextdigital/demo/controller/CajeroController.java create mode 100644 src/main/java/es/nextdigital/demo/controller/CuentaController.java create mode 100644 src/main/java/es/nextdigital/demo/model/Cuenta.java create mode 100644 src/main/java/es/nextdigital/demo/model/Movimiento.java create mode 100644 src/main/java/es/nextdigital/demo/model/Tarjeta.java create mode 100644 src/main/java/es/nextdigital/demo/model/TipoMovimiento .java create mode 100644 src/main/java/es/nextdigital/demo/model/TipoTarjeta.java create mode 100644 src/main/java/es/nextdigital/demo/repository/CuentaRepository.java create mode 100644 src/main/java/es/nextdigital/demo/repository/TarjetaRepository.java create mode 100644 src/main/java/es/nextdigital/demo/service/CajeroService.java create mode 100644 src/main/java/es/nextdigital/demo/service/CuentaService.java create mode 100644 src/main/java/es/nextdigital/demo/util/IbanValidator.java create mode 100644 src/test/java/es/nextdigital/demo/controller/CajeroControllerIntegrationTest.java create mode 100644 src/test/java/es/nextdigital/demo/service/CajeroServiceTest.java create mode 100644 src/test/java/es/nextdigital/demo/util/IbanValidatorTest.java diff --git a/pom.xml b/pom.xml index 4aa2ea7..1952597 100644 --- a/pom.xml +++ b/pom.xml @@ -37,6 +37,10 @@ spring-boot-starter-test test + + org.springframework.security + spring-security-crypto + diff --git a/src/main/java/es/nextdigital/demo/controller/CajeroController.java b/src/main/java/es/nextdigital/demo/controller/CajeroController.java new file mode 100644 index 0000000..c1d3880 --- /dev/null +++ b/src/main/java/es/nextdigital/demo/controller/CajeroController.java @@ -0,0 +1,63 @@ +package es.nextdigital.demo.controller; + +import es.nextdigital.demo.service.CajeroService; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/cajero") +public class CajeroController { + + private final CajeroService cajeroService; + + public CajeroController(CajeroService cajeroService) { + this.cajeroService = cajeroService; + } + + @PostMapping("/retirar") + public String retirar( + @RequestParam Long tarjetaId, + @RequestParam Double cantidad, + @RequestParam(defaultValue = "true") boolean mismoBanco + ) { + cajeroService.retirar(tarjetaId, cantidad, mismoBanco); + return "Retirada realizada correctamente"; + } + + @PostMapping("/ingresar") + public String ingresar( + @RequestParam Long tarjetaId, + @RequestParam Double cantidad, + @RequestParam(defaultValue = "true") boolean mismoBanco + ) { + cajeroService.ingresar(tarjetaId, cantidad, mismoBanco); + return "Ingreso realizado correctamente"; + } + + @PostMapping("/transferir") + public String transferir( + @RequestParam Long tarjetaId, + @RequestParam String ibanDestino, + @RequestParam Double cantidad, + @RequestParam(defaultValue = "true") boolean mismoBancoDestino + ) { + cajeroService.transferir(tarjetaId, ibanDestino, cantidad, mismoBancoDestino); + return "Transferencia realizada correctamente"; + } + + @PostMapping("/activar-tarjeta/{id}") + public String activarTarjeta(@PathVariable Long id) { + cajeroService.activarTarjeta(id); + return "Tarjeta activada correctamente"; + } + + @PostMapping("/cambiar-pin") + public String cambiarPin( + @RequestParam Long tarjetaId, + @RequestParam(required = false) String pinActual, + @RequestParam String pinNuevo + ) { + cajeroService.cambiarPin(tarjetaId, pinActual, pinNuevo); + return "PIN cambiado correctamente"; + } + +} diff --git a/src/main/java/es/nextdigital/demo/controller/CuentaController.java b/src/main/java/es/nextdigital/demo/controller/CuentaController.java new file mode 100644 index 0000000..687de58 --- /dev/null +++ b/src/main/java/es/nextdigital/demo/controller/CuentaController.java @@ -0,0 +1,23 @@ +package es.nextdigital.demo.controller; + +import es.nextdigital.demo.model.Movimiento; +import es.nextdigital.demo.service.CuentaService; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/cuentas") +public class CuentaController { + + private final CuentaService cuentaService; + + public CuentaController(CuentaService cuentaService) { + this.cuentaService = cuentaService; + } + + @GetMapping("/{id}/movimientos") + public List obtenerMovimientos(@PathVariable Long id) { + return cuentaService.obtenerMovimientos(id); + } +} diff --git a/src/main/java/es/nextdigital/demo/model/Cuenta.java b/src/main/java/es/nextdigital/demo/model/Cuenta.java new file mode 100644 index 0000000..b428d85 --- /dev/null +++ b/src/main/java/es/nextdigital/demo/model/Cuenta.java @@ -0,0 +1,32 @@ +package es.nextdigital.demo.model; + +import java.util.ArrayList; +import java.util.List; + +public class Cuenta { + + private Long id; + private String iban; + private Double saldo; + private List movimientos = new ArrayList<>(); + + public Cuenta(Long id, String iban, Double saldo) { + this.id = id; + this.iban = iban; + this.saldo = saldo; + } + + public Cuenta() {} + + public Long getId() { return id; } + public void setId(Long id) { this.id = id; } + + public String getIban() { return iban; } + public void setIban(String iban) { this.iban = iban; } + + public Double getSaldo() { return saldo; } + public void setSaldo(Double saldo) { this.saldo = saldo; } + + public List getMovimientos() { return movimientos; } + public void setMovimientos(List movimientos) { this.movimientos = movimientos; } +} diff --git a/src/main/java/es/nextdigital/demo/model/Movimiento.java b/src/main/java/es/nextdigital/demo/model/Movimiento.java new file mode 100644 index 0000000..42e9edf --- /dev/null +++ b/src/main/java/es/nextdigital/demo/model/Movimiento.java @@ -0,0 +1,43 @@ +package es.nextdigital.demo.model; + +import java.time.LocalDateTime; + +public class Movimiento { + + private Long id; + private LocalDateTime fecha; + private TipoMovimiento tipo; + private Double cantidad; + private String descripcion; + private Long cuentaId; + + public Movimiento(Long id, LocalDateTime fecha, TipoMovimiento tipo, Double cantidad, String descripcion, Long cuentaId) { + this.id = id; + this.fecha = fecha; + this.tipo = tipo; + this.cantidad = cantidad; + this.descripcion = descripcion; + this.cuentaId = cuentaId; + } + + public Movimiento() {} + + // Getters y setters + public Long getId() { return id; } + public void setId(Long id) { this.id = id; } + + public LocalDateTime getFecha() { return fecha; } + public void setFecha(LocalDateTime fecha) { this.fecha = fecha; } + + public TipoMovimiento getTipo() { return tipo; } + public void setTipo(TipoMovimiento tipo) { this.tipo = tipo; } + + public Double getCantidad() { return cantidad; } + public void setCantidad(Double cantidad) { this.cantidad = cantidad; } + + public String getDescripcion() { return descripcion; } + public void setDescripcion(String descripcion) { this.descripcion = descripcion; } + + public Long getCuentaId() { return cuentaId; } + public void setCuentaId(Long cuentaId) { this.cuentaId = cuentaId; } +} diff --git a/src/main/java/es/nextdigital/demo/model/Tarjeta.java b/src/main/java/es/nextdigital/demo/model/Tarjeta.java new file mode 100644 index 0000000..7edbd1a --- /dev/null +++ b/src/main/java/es/nextdigital/demo/model/Tarjeta.java @@ -0,0 +1,63 @@ +package es.nextdigital.demo.model; + +public class Tarjeta { + + private Long id; + private String numero; + private boolean activada; + private TipoTarjeta tipo; + private Double limiteDiario; + private Double creditoMaximo; + private Double creditoDisponible; + private Long cuentaId; + private Double retiradoHoy = 0.0; + private String pinHash; + private boolean pinPendienteCambio = true; + + public Tarjeta(Long id, String numero, TipoTarjeta tipo, Double limiteDiario, + Double creditoMaximo, Long cuentaId) { + this.id = id; + this.numero = numero; + this.activada = false; + this.tipo = tipo; + this.limiteDiario = limiteDiario; + this.creditoMaximo = creditoMaximo; + this.creditoDisponible = creditoMaximo; + this.cuentaId = cuentaId; + } + + public Tarjeta() {} + + public Long getId() { return id; } + public void setId(Long id) { this.id = id; } + + public String getNumero() { return numero; } + public void setNumero(String numero) { this.numero = numero; } + + public boolean getActivada() { return activada; } + public void setActivada(boolean activada) { this.activada = activada; } + + public TipoTarjeta getTipo() { return tipo; } + public void setTipo(TipoTarjeta tipo) { this.tipo = tipo; } + + public Double getLimiteDiario() { return limiteDiario; } + public void setLimiteDiario(Double limiteDiario) { this.limiteDiario = limiteDiario; } + + public Double getCreditoMaximo() { return creditoMaximo; } + public void setCreditoMaximo(Double creditoMaximo) { this.creditoMaximo = creditoMaximo; } + + public Double getCreditoDisponible() { return creditoDisponible; } + public void setCreditoDisponible(Double creditoDisponible) { this.creditoDisponible = creditoDisponible; } + + public Long getCuentaId() { return cuentaId; } + public void setCuentaId(Long cuentaId) { this.cuentaId = cuentaId; } + + public Double getRetiradoHoy() { return retiradoHoy; } + public void setRetiradoHoy(Double retiradoHoy) { this.retiradoHoy = retiradoHoy; } + + public String getPinHash() { return pinHash; } + public void setPinHash(String pinHash) { this.pinHash = pinHash; } + + public boolean getPinPendienteCambio() { return pinPendienteCambio; } + public void setPinPendienteCambio(boolean pinPendienteCambio) { this.pinPendienteCambio = pinPendienteCambio; } +} diff --git a/src/main/java/es/nextdigital/demo/model/TipoMovimiento .java b/src/main/java/es/nextdigital/demo/model/TipoMovimiento .java new file mode 100644 index 0000000..4d8777c --- /dev/null +++ b/src/main/java/es/nextdigital/demo/model/TipoMovimiento .java @@ -0,0 +1,9 @@ +package es.nextdigital.demo.model; + +public enum TipoMovimiento { + INGRESO, + RETIRADA, + COMISION, + TRANSFERENCIA_ENTRANTE, + TRANSFERENCIA_SALIENTE +} diff --git a/src/main/java/es/nextdigital/demo/model/TipoTarjeta.java b/src/main/java/es/nextdigital/demo/model/TipoTarjeta.java new file mode 100644 index 0000000..eb09f8a --- /dev/null +++ b/src/main/java/es/nextdigital/demo/model/TipoTarjeta.java @@ -0,0 +1,6 @@ +package es.nextdigital.demo.model; + +public enum TipoTarjeta { + DEBITO, + CREDITO +} \ No newline at end of file diff --git a/src/main/java/es/nextdigital/demo/repository/CuentaRepository.java b/src/main/java/es/nextdigital/demo/repository/CuentaRepository.java new file mode 100644 index 0000000..f21ce3d --- /dev/null +++ b/src/main/java/es/nextdigital/demo/repository/CuentaRepository.java @@ -0,0 +1,26 @@ +package es.nextdigital.demo.repository; + +import es.nextdigital.demo.model.Cuenta; +import org.springframework.stereotype.Repository; + +import java.util.HashMap; +import java.util.Map; + +@Repository +public class CuentaRepository { + + private final Map cuentas = new HashMap<>(); + + public Cuenta findById(Long id) { + return cuentas.get(id); + } + + public void save(Cuenta cuenta) { + cuentas.put(cuenta.getId(), cuenta); + } + + public Collection findAll() { + return cuentas.values(); + } + +} diff --git a/src/main/java/es/nextdigital/demo/repository/TarjetaRepository.java b/src/main/java/es/nextdigital/demo/repository/TarjetaRepository.java new file mode 100644 index 0000000..54bdc6f --- /dev/null +++ b/src/main/java/es/nextdigital/demo/repository/TarjetaRepository.java @@ -0,0 +1,21 @@ +package es.nextdigital.demo.repository; + +import es.nextdigital.demo.model.Tarjeta; +import org.springframework.stereotype.Repository; + +import java.util.HashMap; +import java.util.Map; + +@Repository +public class TarjetaRepository { + + private final Map tarjetas = new HashMap<>(); + + public Tarjeta findById(Long id) { + return tarjetas.get(id); + } + + public void save(Tarjeta t) { + tarjetas.put(t.getId(), t); + } +} diff --git a/src/main/java/es/nextdigital/demo/service/CajeroService.java b/src/main/java/es/nextdigital/demo/service/CajeroService.java new file mode 100644 index 0000000..3afa832 --- /dev/null +++ b/src/main/java/es/nextdigital/demo/service/CajeroService.java @@ -0,0 +1,228 @@ +package es.nextdigital.demo.service; + +import es.nextdigital.demo.model.*; +import es.nextdigital.demo.repository.CuentaRepository; +import es.nextdigital.demo.repository.TarjetaRepository; +import org.springframework.stereotype.Service; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; + +import java.time.LocalDateTime; + +@Service +public class CajeroService { + + private final CuentaRepository cuentaRepository; + private final TarjetaRepository tarjetaRepository; + + private final BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); + + public CajeroService(CuentaRepository cuentaRepository, TarjetaRepository tarjetaRepository) { + this.cuentaRepository = cuentaRepository; + this.tarjetaRepository = tarjetaRepository; + } + + private Tarjeta obtenerTarjetaValida(Long tarjetaId) { + Tarjeta tarjeta = tarjetaRepository.findById(tarjetaId); + + if (tarjeta == null) { + throw new RuntimeException("Tarjeta no encontrada"); + } + + if (!tarjeta.isActivada()) { + throw new RuntimeException("La tarjeta no está activada"); + } + + if (tarjeta.getPinPendienteCambio()) { + throw new RuntimeException("Debe cambiar el PIN antes de realizar operaciones"); + } + + return tarjeta; + } + + private Cuenta obtenerCuentaAsociada(Tarjeta tarjeta) { + Cuenta cuenta = cuentaRepository.findById(tarjeta.getCuentaId()); + + if (cuenta == null) { + throw new RuntimeException("La cuenta asociada no existe"); + } + + return cuenta; + } + + + public void retirar(Long tarjetaId, Double cantidad, boolean esMismoBanco) { + + Tarjeta tarjeta = obtenerTarjetaValida(tarjetaId); + Cuenta cuenta = obtenerCuentaAsociada(tarjeta); + + if (tarjeta.getRetiradoHoy() + cantidad > tarjeta.getLimiteDiario()) { + throw new RuntimeException("Límite diario de la tarjeta superado"); + } + + double comision = esMismoBanco ? 0.0 : cantidad * 0.02; + double total = cantidad + comision; + + if (tarjeta.getTipo() == TipoTarjeta.DEBITO) { + if (cuenta.getSaldo() < total) { + throw new RuntimeException("Saldo insuficiente"); + } + cuenta.setSaldo(cuenta.getSaldo() - total); + } + + if (tarjeta.getTipo() == TipoTarjeta.CREDITO) { + if (tarjeta.getCreditoDisponible() < total) { + throw new RuntimeException("Crédito insuficiente"); + } + tarjeta.setCreditoDisponible(tarjeta.getCreditoDisponible() - total); + } + + tarjeta.setRetiradoHoy(tarjeta.getRetiradoHoy() + cantidad); + + Movimiento mov = new Movimiento( + System.currentTimeMillis(), + LocalDateTime.now(), + TipoMovimiento.RETIRADA, + cantidad, + esMismoBanco ? + "Retirada de efectivo" : + "Retirada en cajero externo (incluye comisión)", + cuenta.getId() + ); + + cuenta.getMovimientos().add(mov); + + tarjetaRepository.save(tarjeta); + cuentaRepository.save(cuenta); + } + + public void ingresar(Long tarjetaId, Double cantidad, boolean esMismoBanco) { + + Tarjeta tarjeta = obtenerTarjetaValida(tarjetaId); + + if (!esMismoBanco) { + throw new RuntimeException("No se pueden ingresar fondos desde cajeros de otras entidades"); + } + + Cuenta cuenta = obtenerCuentaAsociada(tarjeta); + + cuenta.setSaldo(cuenta.getSaldo() + cantidad); + + Movimiento mov = new Movimiento( + System.currentTimeMillis(), + LocalDateTime.now(), + TipoMovimiento.INGRESO, + cantidad, + "Ingreso de efectivo en cajero del banco", + cuenta.getId() + ); + + cuenta.getMovimientos().add(mov); + + cuentaRepository.save(cuenta); + } + + public void transferir(Long tarjetaId, String ibanDestino, Double cantidad, boolean esMismoBancoDestino) { + + Tarjeta tarjeta = obtenerTarjetaValida(tarjetaId); + Cuenta cuentaOrigen = obtenerCuentaAsociada(tarjeta); + + if (!IbanValidator.validarIBAN(ibanDestino)) { + throw new RuntimeException("IBAN destino no válido"); + } + + Cuenta cuentaDestino = null; + for (Cuenta c : cuentaRepository.findAll()) { + if (c.getIban().equals(ibanDestino)) { + cuentaDestino = c; + break; + } + } + + if (cuentaDestino == null) { + throw new RuntimeException("Cuenta destino no encontrada"); + } + + double comision = esMismoBancoDestino ? 0.0 : cantidad * 0.02; + double total = cantidad + comision; + + if (tarjeta.getTipo() == TipoTarjeta.DEBITO) { + if (cuentaOrigen.getSaldo() < total) { + throw new RuntimeException("Saldo insuficiente para transferencia"); + } + cuentaOrigen.setSaldo(cuentaOrigen.getSaldo() - total); + } + + if (tarjeta.getTipo() == TipoTarjeta.CREDITO) { + if (tarjeta.getCreditoDisponible() < total) { + throw new RuntimeException("Crédito insuficiente"); + } + tarjeta.setCreditoDisponible(tarjeta.getCreditoDisponible() - total); + } + + cuentaDestino.setSaldo(cuentaDestino.getSaldo() + cantidad); + + Movimiento movSalida = new Movimiento( + System.currentTimeMillis(), + LocalDateTime.now(), + TipoMovimiento.TRANSFERENCIA_SALIENTE, + cantidad, + esMismoBancoDestino ? + "Transferencia a cuenta del mismo banco" : + "Transferencia a otro banco (incluye comisión)", + cuentaOrigen.getId() + ); + cuentaOrigen.getMovimientos().add(movSalida); + + Movimiento movEntrada = new Movimiento( + System.currentTimeMillis() + 1, + LocalDateTime.now(), + TipoMovimiento.TRANSFERENCIA_ENTRANTE, + cantidad, + "Transferencia recibida", + cuentaDestino.getId() + ); + cuentaDestino.getMovimientos().add(movEntrada); + + cuentaRepository.save(cuentaOrigen); + cuentaRepository.save(cuentaDestino); + tarjetaRepository.save(tarjeta); + } + + public void activarTarjeta(Long tarjetaId) { + Tarjeta tarjeta = tarjetaRepository.findById(tarjetaId); + + if (tarjeta == null) { + throw new RuntimeException("Tarjeta no encontrada"); + } + + if (tarjeta.isActivada()) { + throw new RuntimeException("La tarjeta ya estaba activada"); + } + + tarjeta.setActivada(true); + tarjeta.setPinPendienteCambio(true); + tarjetaRepository.save(tarjeta); + } + + public void cambiarPin(Long tarjetaId, String pinActual, String pinNuevo) { + + Tarjeta tarjeta = tarjetaRepository.findById(tarjetaId); + + if (tarjeta == null) { + throw new RuntimeException("Tarjeta no encontrada"); + } + + if (!tarjeta.getPinPendienteCambio()) { + if (!encoder.matches(pinActual, tarjeta.getPinHash())) { + throw new RuntimeException("El PIN actual no es correcto"); + } + } + + // Guardamos el nuevo PIN hasheado + tarjeta.setPinHash(encoder.encode(pinNuevo)); + tarjeta.setPinPendienteCambio(false); + + tarjetaRepository.save(tarjeta); + } + +} \ No newline at end of file diff --git a/src/main/java/es/nextdigital/demo/service/CuentaService.java b/src/main/java/es/nextdigital/demo/service/CuentaService.java new file mode 100644 index 0000000..f74e76f --- /dev/null +++ b/src/main/java/es/nextdigital/demo/service/CuentaService.java @@ -0,0 +1,28 @@ +package es.nextdigital.demo.service; + +import es.nextdigital.demo.model.Cuenta; +import es.nextdigital.demo.model.Movimiento; +import es.nextdigital.demo.repository.CuentaRepository; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public class CuentaService { + + private final CuentaRepository cuentaRepository; + + public CuentaService(CuentaRepository cuentaRepository) { + this.cuentaRepository = cuentaRepository; + } + + public List obtenerMovimientos(Long cuentaId) { + Cuenta cuenta = cuentaRepository.findById(cuentaId); + + if (cuenta == null) { + throw new RuntimeException("La cuenta no existe"); + } + + return cuenta.getMovimientos(); + } +} diff --git a/src/main/java/es/nextdigital/demo/util/IbanValidator.java b/src/main/java/es/nextdigital/demo/util/IbanValidator.java new file mode 100644 index 0000000..584e7c1 --- /dev/null +++ b/src/main/java/es/nextdigital/demo/util/IbanValidator.java @@ -0,0 +1,11 @@ +package es.nextdigital.demo.util; + +public class IbanValidator { + + public static boolean validarIBAN(String iban) { + if (iban == null || iban.length() < 15 || iban.length() > 34) { + return false; + } + return iban.matches("[A-Z]{2}[0-9A-Z]{13,32}"); + } +} \ No newline at end of file diff --git a/src/test/java/es/nextdigital/demo/controller/CajeroControllerIntegrationTest.java b/src/test/java/es/nextdigital/demo/controller/CajeroControllerIntegrationTest.java new file mode 100644 index 0000000..d0288f9 --- /dev/null +++ b/src/test/java/es/nextdigital/demo/controller/CajeroControllerIntegrationTest.java @@ -0,0 +1,47 @@ +package es.nextdigital.demo.controller; + +import es.nextdigital.demo.model.*; +import es.nextdigital.demo.repository.CuentaRepository; +import es.nextdigital.demo.repository.TarjetaRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.web.servlet.MockMvc; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@AutoConfigureMockMvc +public class CajeroControllerIntegrationTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private CuentaRepository cuentaRepository; + + @Autowired + private TarjetaRepository tarjetaRepository; + + @BeforeEach + void setup() { + Cuenta c = new Cuenta(1L, "ES123", 1000.0); + cuentaRepository.save(c); + + Tarjeta t = new Tarjeta(1L, "1111", TipoTarjeta.DEBITO, 500.0, 0.0, 1L); + t.setActivada(true); + t.setPinPendienteCambio(false); + tarjetaRepository.save(t); + } + + @Test + void retirarEndpoint_DebeDevolver200() throws Exception { + mockMvc.perform(post("/cajero/retirar") + .param("tarjetaId", "1") + .param("cantidad", "100")) + .andExpect(status().isOk()); + } +} diff --git a/src/test/java/es/nextdigital/demo/service/CajeroServiceTest.java b/src/test/java/es/nextdigital/demo/service/CajeroServiceTest.java new file mode 100644 index 0000000..a16dd0e --- /dev/null +++ b/src/test/java/es/nextdigital/demo/service/CajeroServiceTest.java @@ -0,0 +1,50 @@ +package es.nextdigital.demo.service; + +import es.nextdigital.demo.model.*; +import es.nextdigital.demo.repository.CuentaRepository; +import es.nextdigital.demo.repository.TarjetaRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class CajeroServiceTest { + + private CuentaRepository cuentaRepository; + private TarjetaRepository tarjetaRepository; + private CajeroService cajeroService; + + @BeforeEach + void setup() { + cuentaRepository = new CuentaRepository(); + tarjetaRepository = new TarjetaRepository(); + cajeroService = new CajeroService(cuentaRepository, tarjetaRepository); + + // Cuenta inicial + Cuenta c = new Cuenta(1L, "ES123", 1000.0); + cuentaRepository.save(c); + + // Tarjeta activada + Tarjeta t = new Tarjeta(1L, "1111", TipoTarjeta.DEBITO, 500.0, 0.0, 1L); + t.setActivada(true); + t.setPinPendienteCambio(false); + tarjetaRepository.save(t); + } + + @Test + void retirar_DebeRestarSaldoCorrectamente() { + cajeroService.retirar(1L, 100.0, true); + + Cuenta cuenta = cuentaRepository.findById(1L); + assertEquals(900.0, cuenta.getSaldo()); + assertEquals(1, cuenta.getMovimientos().size()); + assertEquals(TipoMovimiento.RETIRADA, cuenta.getMovimientos().get(0).getTipo()); + } + + @Test + void retirar_SaldoInsuficiente_DebeLanzarError() { + assertThrows(RuntimeException.class, () -> { + cajeroService.retirar(1L, 2000.0, true); + }); + } +} \ No newline at end of file diff --git a/src/test/java/es/nextdigital/demo/util/IbanValidatorTest.java b/src/test/java/es/nextdigital/demo/util/IbanValidatorTest.java new file mode 100644 index 0000000..3479ee2 --- /dev/null +++ b/src/test/java/es/nextdigital/demo/util/IbanValidatorTest.java @@ -0,0 +1,23 @@ +package es.nextdigital.demo.service; + +import es.nextdigital.demo.model.*; +import es.nextdigital.demo.repository.CuentaRepository; +import es.nextdigital.demo.repository.TarjetaRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class IbanValidatorTest { + + @Test + void validarIban_InvalidoDebeFallar() { + assertFalse(IbanValidator.validarIBAN("1234")); + } + + @Test + void validarIban_ValidoDebePasar() { + assertTrue(IbanValidator.validarIBAN("ES9121000418450200051332")); + } + +} \ No newline at end of file