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
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public class ProductosController {

@PostMapping
@Transactional
@PreAuthorize("hasRole('DEV')")
@PreAuthorize("hasAnyRole('ADMIN', 'DEV')")
public ResponseEntity<DatosRespuestaProducto> registrar(@RequestBody @Valid DatosRegistroProducto datos, UriComponentsBuilder uriComponentsBuilder) {
Categoria categoria = categoriaRepository.findById(datos.id_categoria()).orElseThrow();
// El nombre es único: si existe un producto con borrado suave se reutiliza ese registro
Expand All @@ -63,7 +63,7 @@ public ResponseEntity<List<DatosRespuestaProducto>> listar() {

@PutMapping("/{id}")
@Transactional
@PreAuthorize("hasRole('DEV')")
@PreAuthorize("hasAnyRole('ADMIN', 'DEV')")
public ResponseEntity<DatosRespuestaProducto> actualizar(@PathVariable Long id,
@RequestBody @Valid DatosActualizacionProducto datos) {
Producto producto = repository.findById(id).orElseThrow();
Expand All @@ -76,7 +76,7 @@ public ResponseEntity<DatosRespuestaProducto> actualizar(@PathVariable Long id,

@DeleteMapping("/{id}")
@Transactional
@PreAuthorize("hasRole('DEV')")
@PreAuthorize("hasAnyRole('ADMIN', 'DEV')")
public ResponseEntity<Void> eliminar(@PathVariable Long id) {
Producto producto = repository.findById(id).orElseThrow();
if (ordenDetalleRepository.existePorProducto(id)) {
Expand Down Expand Up @@ -164,7 +164,7 @@ public ResponseEntity<Void> archivarDelDia(@PathVariable Long id) {

@PatchMapping("/{id}/dia")
@Transactional
@PreAuthorize("hasAnyRole('DEV', 'REPARTIDOR')")
@PreAuthorize("hasAnyRole('ADMIN', 'DEV', 'REPARTIDOR')")
public ResponseEntity<?> actualizarDia(@PathVariable Long id, @RequestBody DatosActualizacionDia datos) {
Producto producto = repository.findById(id).orElseThrow();
if (Boolean.TRUE.equals(datos.disponibilidad()) && !producto.getDisponibilidad()) {
Expand All @@ -186,7 +186,7 @@ public ResponseEntity<?> actualizarDia(@PathVariable Long id, @RequestBody Datos

@PutMapping("/desactivar-dia/{categoriaId}")
@Transactional
@PreAuthorize("hasAnyRole('DEV', 'REPARTIDOR')")
@PreAuthorize("hasAnyRole('ADMIN', 'DEV', 'REPARTIDOR')")
public ResponseEntity<Void> desactivarDia(@PathVariable Long categoriaId) {
repository.desactivarPorCategoria(categoriaId);
return ResponseEntity.noContent().build();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package restaurante.api.producto;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.security.access.prepost.PreAuthorize;
import restaurante.api.controller.ordenes.ProductosController;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

/**
* Quién puede tocar la carta.
*
* El 2026-08-11 se le dio al ADMIN la pantalla de Platillos, que hasta entonces
* era solo de DEV. El frontend por sí solo no alcanza: si el botón aparece pero
* el backend responde 403, la pantalla miente. Este test vigila el permiso real,
* que es el del servidor.
*
* Compara conjuntos EXACTOS a propósito, no "contiene". Así avisa en las dos
* direcciones: si alguien le quita el permiso al ADMIN y la pantalla se rompe,
* y —más importante— si alguien se lo regala de más a un MESERO o a COCINA y
* cualquiera desde una tablet puede borrar platillos de la carta.
*
* No levanta el contexto de Spring: lee las anotaciones de la clase, que es de
* donde Spring saca la regla. Si el método se renombra, también falla.
*/
class PermisosProductosTest {

/** Los nombres de rol que aparecen entrecomillados dentro de hasRole/hasAnyRole. */
private static final Pattern ROL = Pattern.compile("'([A-Z_]+)'");

private static Set<String> rolesDe(String nombreMetodo) {
List<Method> metodos = Arrays.stream(ProductosController.class.getDeclaredMethods())
.filter(m -> m.getName().equals(nombreMetodo))
.toList();

assertEquals(1, metodos.size(),
"Se esperaba exactamente un método llamado '" + nombreMetodo + "' en ProductosController");

PreAuthorize anotacion = metodos.get(0).getAnnotation(PreAuthorize.class);
assertNotNull(anotacion,
nombreMetodo + " se quedó sin @PreAuthorize: quedaría abierto a cualquier usuario con sesión");

Set<String> roles = new LinkedHashSet<>();
Matcher m = ROL.matcher(anotacion.value());
while (m.find()) {
roles.add(m.group(1));
}
assertTrue(!roles.isEmpty(),
nombreMetodo + " tiene un @PreAuthorize que no nombra ningún rol: " + anotacion.value());
return roles;
}

@Test
@DisplayName("Dar de alta, editar y borrar platillos: solo el dueño (ADMIN) y DEV")
void administrarLaCarta_SoloAdminYDev() {
Set<String> esperado = Set.of("ADMIN", "DEV");

assertEquals(esperado, rolesDe("registrar"));
assertEquals(esperado, rolesDe("actualizar"));
assertEquals(esperado, rolesDe("eliminar"));
}

@Test
@DisplayName("Activar y apagar los platillos del día: ADMIN, DEV y el repartidor")
void menuDelDia_AdminDevYRepartidor() {
Set<String> esperado = Set.of("ADMIN", "DEV", "REPARTIDOR");

// El ADMIN ya veía "Platillos del Día" en su menú, pero estos dos endpoints
// no lo aceptaban: la pantalla cargaba y fallaba al activar. Se corrigió el
// 2026-08-11 junto con lo de arriba.
assertEquals(esperado, rolesDe("actualizarDia"));
assertEquals(esperado, rolesDe("desactivarDia"));

assertEquals(esperado, rolesDe("registrarDelDia"));
assertEquals(esperado, rolesDe("archivarDelDia"));
}

@Test
@DisplayName("Leer la carta sí es de todos los que toman órdenes: el mesero no puede quedarse sin menú")
void listar_IncluyeAlMesero() {
assertEquals(Set.of("ADMIN", "DEV", "MESERO", "REPARTIDOR"), rolesDe("listar"));
}
}
Loading