package middlewares import ( "net/http/httptest" "testing" "github.com/gofiber/fiber/v2" ) // Este archivo prueba la única pieza que separa los datos de un cliente de los // de otro. Un fallo acá no es un bug de pantalla: es el cliente A leyendo las // conversaciones, el conocimiento y las claves del cliente B. // Se prueba a través de una petición real en vez de fabricar un contexto a // mano: es el mismo camino que recorre el middleware en producción. func permiteVer(t *testing.T, scope interface{}, poner bool, tenantID uint) bool { t.Helper() app := fiber.New() var permitido bool app.Get("/x", func(c *fiber.Ctx) error { if poner { c.Locals(umindScopeKey, scope) } permitido = UmindPuedeVerTenant(c, tenantID) return c.SendStatus(200) }) req := httptest.NewRequest("GET", "/x", nil) if _, err := app.Test(req); err != nil { t.Fatal(err) } return permitido } func scopeResuelto(t *testing.T, scope interface{}) ([]uint, bool) { t.Helper() app := fiber.New() var ids []uint var esStaff bool app.Get("/x", func(c *fiber.Ctx) error { c.Locals(umindScopeKey, scope) ids, esStaff = UmindScopeDe(c) return c.SendStatus(200) }) if _, err := app.Test(httptest.NewRequest("GET", "/x", nil)); err != nil { t.Fatal(err) } return ids, esStaff } func TestUnClienteNoVeElEspacioDeOtro(t *testing.T) { casos := []struct { nombre string scope interface{} poner bool tenant uint permitir bool }{ // El caso que importa: alcance sobre 1 y 7, pregunta por el 9. {"espacio ajeno", []uint{1, 7}, true, 9, false}, {"espacio propio", []uint{1, 7}, true, 7, true}, {"otro espacio propio", []uint{1, 7}, true, 1, true}, // Un cliente sin espacios no ve absolutamente nada. {"cliente sin espacios", []uint{}, true, 1, false}, // nil = staff, ve todo. Es la distinción más delicada del archivo: // confundir "lista vacía" con nil le daría acceso total a un cliente. {"staff", []uint(nil), true, 999, true}, // Ruta sin middleware de scope: fail-closed. Que se rompa visible en // vez de servir los datos de todos por omisión. {"sin scope en el contexto", nil, false, 1, false}, // Un valor de otro tipo en la clave tampoco puede abrir la puerta. {"scope corrupto", "todos", true, 1, false}, } for _, cs := range casos { got := permiteVer(t, cs.scope, cs.poner, cs.tenant) if got != cs.permitir { t.Errorf("%s: UmindPuedeVerTenant(%d) = %v, esperaba %v", cs.nombre, cs.tenant, got, cs.permitir) } } } // La diferencia entre nil y slice vacío es la que decide si alguien es staff. // Si un cliente sin espacios quedara como nil, pasaría a ver todo. func TestSliceVacioNoEsStaff(t *testing.T) { ids, esStaff := scopeResuelto(t, []uint{}) if esStaff { t.Error("un cliente sin espacios NO puede resolver como staff") } if len(ids) != 0 { t.Errorf("esperaba lista vacía, dio %v", ids) } if _, esStaff := scopeResuelto(t, []uint(nil)); !esStaff { t.Error("nil tiene que resolver como staff: es como lo deja UmindScopeStaff") } }