Prueba reglas de seguridad

A medida que compiles tu app, te recomendamos bloquear el acceso a tu base de datos de Firestore. Sin embargo, antes del lanzamiento, necesitarás reglas de seguridad de Firestore más matizadas. Con el emulador de Firestore, además de prototipar y probar las funciones y el comportamiento general de tu app, puedes escribir pruebas de unidades que verifiquen el comportamiento de las reglas de seguridad de Firestore.

Guía de inicio rápido

Si quieres consultar algunos casos de prueba con reglas simples, usa la guía de inicio rápido de ejemplo.

Comprende las reglas de seguridad de Firestore

Implementa Firebase Authentication y las reglas de seguridad de Firestore para la autenticación, autorización y validación de datos sin servidores cuando uses las bibliotecas cliente para dispositivos móviles y la Web.

Las reglas de seguridad de Firestore incluyen dos partes:

  1. Una declaración match que identifica los documentos en tu base de datos
  2. Una expresión allow que controla el acceso a los documentos

Firebase Authentication verifica las credenciales de los usuarios y proporciona una base para los sistemas de acceso basados en usuarios y funciones.

Todas las solicitudes que se envíen a la base de datos desde una biblioteca cliente de Firestore para dispositivos móviles o la Web se evalúan en función de las reglas de seguridad antes de leer o escribir datos. Si las reglas rechazan el acceso a alguna de las rutas de los documentos especificados, la solicitud completa resulta errónea.

Obtén más información sobre las reglas de seguridad de Firestore en Cómo comenzar con las reglas de seguridad de Firestore.

Instala el emulador

Para instalar el emulador de Firestore, usa Firebase CLI y ejecuta el comando que aparece a continuación:

firebase setup:emulators:firestore

Ejecuta el emulador

Comienza por inicializar un proyecto de Firebase en tu directorio de trabajo. Este es un primer paso común para usar Firebase CLI.

firebase init

Inicia el emulador con el siguiente comando. El emulador se ejecutará hasta que finalices el proceso:

firebase emulators:start --only firestore

En muchos casos, es recomendable que inicies el emulador, ejecutes una prueba y, luego, cierres el emulador después de que hayas ejecutado las pruebas. Puedes hacerlo de manera sencilla con el comando emulators:exec, como en el ejemplo:

firebase emulators:exec --only firestore "./my-test-script.sh"

Cuando se inicie, el emulador intentará ejecutarse en un puerto predeterminado (8080). Para cambiar el puerto del emulador, modifica la sección "emulators" del archivo firebase.json:

{
  // ...
  "emulators": {
    "firestore": {
      "port": "YOUR_PORT"
    }
  }
}

Antes de que ejecutes el emulador

Antes de comenzar a usar el emulador, ten en cuenta lo siguiente:

  • Inicialmente el emulador cargará las reglas especificadas en el campo firestore.rules del archivo firebase.json. Este espera el nombre de un archivo local con tus reglas de seguridad de Firestore y las aplica a todos los proyectos. Si no proporcionas la ruta de un archivo local o no usas el método loadFirestoreRules como se describe a continuación, el emulador trata todos los proyectos como si tuvieran reglas abiertas.
  • Si bien la mayoría de los SDK de Firebase funcionan con los emuladores de forma directa, solo la biblioteca @firebase/rules-unit-testing admite la simulación de auth en las reglas de seguridad, lo que facilita las pruebas de unidades. Además, la biblioteca admite algunas funciones específicas del emulador, como borrar todos los datos, según se detalla a continuación.
  • Los emuladores también aceptarán tokens de producción de Firebase Auth proporcionados a través de SDK cliente y evaluarán las reglas en consecuencia, lo que te permite conectar tu aplicación directamente a los emuladores en pruebas de integración y manuales.

Cómo ejecutar pruebas de unidades locales

Ejecuta pruebas de unidades locales con el SDK de JavaScript v9

Firebase distribuye una biblioteca de pruebas de unidades de reglas de seguridad con el SDK de JavaScript de la versión 9 y el SDK de la versión 8. Las API de la biblioteca son muy diferentes. Recomendamos la biblioteca de prueba de la v9, que es más optimizada y requiere menos configuración para conectarse a los emuladores y, por lo tanto, evitar el uso accidental de recursos de producción por accidente. Para la retrocompatibilidad, seguimos haciendo que la biblioteca de pruebas v8 esté disponible.

Usa el módulo @firebase/rules-unit-testing para interactuar con el emulador que se ejecuta de manera local. Si recibes errores ECONNREFUSED o de tiempo de espera, vuelve a verificar que se esté ejecutando el emulador.

Recomendamos usar una versión reciente de Node.js, de modo que puedas utilizar la notación async/await. Casi todo comportamiento que quieras probar involucra funciones asíncronas. El módulo de pruebas está diseñado para funcionar con un código basado en promesas.

La biblioteca de pruebas de unidades de reglas v9 siempre conoce los emuladores y nunca toca tus recursos de producción.

Importarás la biblioteca con instrucciones de importación modulares v9. como los siguientes:

import {
  assertFails,
  assertSucceeds,
  initializeTestEnvironment,
  RulesTestEnvironment,
} from "@firebase/rules-unit-testing"

// Use `const { … } = require("@firebase/rules-unit-testing")` if imports are not supported
// Or we suggest `const testing = require("@firebase/rules-unit-testing")` if necessary.

Una vez importada, la implementación de pruebas de unidades implica lo siguiente:

  • Crear y configurar un objeto RulesTestEnvironment con una llamada a initializeTestEnvironment
  • Configurar datos de prueba sin activar reglas mediante un método útil que te permita omitirlos temporalmente, RulesTestEnvironment.withSecurityRulesDisabled.
  • Configurar los conjuntos de pruebas y los hooks antes o después de la prueba con llamadas para limpiar los datos y el entorno de pruebas, como RulesTestEnvironment.cleanup() o RulesTestEnvironment.clearFirestore()
  • Implementar casos de prueba que imitan estados de autenticación mediante RulesTestEnvironment.authenticatedContext y RulesTestEnvironment.unauthenticatedContext

Métodos comunes y funciones de utilidad

Consulta también los métodos de prueba específicos del emulador en la versión 9 del SDK.

initializeTestEnvironment() => RulesTestEnvironment

Esta función inicializa un entorno de prueba para la prueba de unidades de reglas. Llama a esta función primero para realizar la configuración de prueba. Si la ejecución es correcta, se deben ejecutar emuladores.

La función acepta un objeto opcional que define un TestEnvironmentConfig, que puede consistir en un ID del proyecto y ajustes de configuración del emulador.

let testEnv = await initializeTestEnvironment({
  projectId: "demo-project-1234",
  firestore: {
    rules: fs.readFileSync("firestore.rules", "utf8"),
  },
});

RulesTestEnvironment.authenticatedContext({ user_id: string, tokenOptions?: TokenOptions }) => RulesTestContext

Este método crea un RulesTestContext, que se comporta como un usuario de Authentication autenticado. Las solicitudes creadas a través del contexto mostrado tendrán un token de autenticación ficticio adjunto. De manera opcional, pasa un objeto que defina reclamaciones personalizadas o anule las cargas útiles del token de autenticación.

Usa el objeto de contexto de prueba que se muestra en tus pruebas para acceder a cualquier instancia del emulador configurada, incluidas las configuradas con initializeTestEnvironment.

// Assuming a Firestore app and the Firestore emulator for this example
import { setDoc } from "firebase/firestore";

const alice = testEnv.authenticatedContext("alice", { … });
// Use the Firestore instance associated with this context
await assertSucceeds(setDoc(alice.firestore(), '/users/alice'), { ... });

RulesTestEnvironment.unauthenticatedContext() => RulesTestContext

Este método crea un RulesTestContext, que se comporta como un cliente que no accedió a través de Authentication. Las solicitudes creadas a través del contexto devuelto no tendrán tokens de Firebase Auth adjuntos.

Usa el objeto de contexto de prueba que se muestra en tus pruebas para acceder a cualquier instancia del emulador configurada, incluidas las configuradas con initializeTestEnvironment.

// Assuming a Cloud Storage app and the Storage emulator for this example
import { getStorage, ref, deleteObject } from "firebase/storage";

const alice = testEnv.unauthenticatedContext();

// Use the Cloud Storage instance associated with this context
const desertRef = ref(alice.storage(), 'images/desert.jpg');
await assertSucceeds(deleteObject(desertRef));

RulesTestEnvironment.withSecurityRulesDisabled()

Ejecuta una función de configuración de prueba con un contexto que se comporte como si estuvieran inhabilitadas las reglas de seguridad.

Este método toma una función de devolución de llamada, que toma el contexto que omite las reglas de seguridad y muestra una promesa. El contexto se destruirá una vez que la promesa se resuelva o rechace.

RulesTestEnvironment.cleanup()

Este método destruye todos los RulesTestContexts creados en el entorno de prueba y limpia los recursos subyacentes, lo que permite una salida limpia.

Este método no cambia el estado de los emuladores de ninguna manera. Para restablecer datos entre pruebas, usa el método de eliminación de datos específico del emulador de la app.

assertSucceeds(pr: Promise<any>)) => Promise<any>

Esta es una función de utilidad de caso de prueba.

La función afirma que la promesa proporcionada que ajusta una operación del emulador se resolverá sin infringir las reglas de seguridad.

await assertSucceeds(setDoc(alice.firestore(), '/users/alice'), { ... });

assertFails(pr: Promise<any>)) => Promise<any>

Esta es una función de utilidad de caso de prueba.

La función afirma que la promesa proporcionada de una operación del emulador se rechazará con una infracción de las reglas de seguridad.

await assertFails(setDoc(alice.firestore(), '/users/bob'), { ... });

Métodos específicos del emulador

Consulta también los métodos de prueba y las funciones de utilidad comunes en el SDK v9.

RulesTestEnvironment.clearFirestore() => Promise<void>

Este método borra los datos de la base de datos de Firestore que pertenecen al projectId configurado para el emulador de Firestore.

RulesTestContext.firestore(settings?: Firestore.FirestoreSettings) => Firestore;

Con este método, se obtiene una instancia de Firestore para este contexto de prueba. La instancia del SDK de cliente de Firebase JS que se muestra se puede usar con las API del SDK de cliente (compatibles con la v9 modular) o la v9.

Visualiza las evaluaciones de las reglas

El emulador de Firestore te permite visualizar solicitudes de clientes en la IU de Emulator Suite, incluido el seguimiento de la evaluación de las reglas de seguridad de Firebase.

Abre la pestaña Solicitudes de Firestore a fin de ver la secuencia de evaluación detallada para cada solicitud.

En el monitor de solicitudes del emulador de Firestore, se muestran evaluaciones de reglas de seguridad

Genera informes de prueba

Luego de ejecutar un conjunto de pruebas, puedes acceder a los informes de cobertura de pruebas que muestran cómo se evaluaron las reglas de seguridad.

Para obtenerlos, consulta un extremo expuesto en el emulador mientras se está ejecutando. Usa la siguiente URL para obtener una versión compatible con navegadores:

http://localhost:8080/emulator/v1/projects/<project_id>:ruleCoverage.html

Esto divide tus reglas en expresiones y subexpresiones sobre las que puedes desplazar el mouse para obtener más información, como la cantidad de evaluaciones y los valores mostrados. Si quieres acceder a la versión JSON sin procesar de los datos, incluye la siguiente URL en la consulta:

http://localhost:8080/emulator/v1/projects/<project_id>:ruleCoverage

Diferencias entre el emulador y el entorno de producción

  1. No es necesario crear un proyecto de Firestore de manera explícita. El emulador crea automáticamente cualquier instancia a la que se acceda.
  2. El emulador de Firestore no funciona con el flujo normal de Firebase Authentication. En su lugar, en el SDK de prueba de Firebase, proporcionamos el método initializeTestApp() en la biblioteca rules-unit-testing, que incluye un campo auth. El controlador de Firebase creado con este método se comportará como si se hubiera autenticado correctamente como cualquier entidad proporcionada. Si pasas null, se comportará como un usuario no autenticado (por ejemplo, fallarán las reglas auth != null).

Soluciona problemas conocidos

Mientras usas el emulador de Firestore, puedes tener los siguientes problemas conocidos. Sigue las instrucciones de más abajo para solucionar cualquier problema de comportamiento irregular que experimentes. Estas notas se escriben teniendo en cuenta la biblioteca de pruebas de unidades de las reglas de seguridad, pero los enfoques generales se aplican a cualquier SDK de Firebase.

El comportamiento de la prueba es incoherente

Si tus pruebas pasan y fallan ocasionalmente, incluso sin ningún cambio en ellas, es posible que debas verificar si están secuenciadas de manera correcta. La mayoría de las interacciones con el emulador son asíncronas, por lo tanto, vuelve a verificar que todo el código asíncrono esté secuenciado de forma correcta. Puedes solucionar problemas de secuencia mediante cadenas de promesas o con el uso libre de la notación await.

En particular, revisa las siguientes operaciones asíncronas:

  • Establecer reglas de seguridad (por ejemplo, con initializeTestEnvironment)
  • Leer y escribir datos (por ejemplo, con db.collection("users").doc("alice").get())
  • Usar aserciones operacionales, como assertSucceeds y assertFails

Las pruebas solo pasan la primera vez que cargas el emulador

El emulador es con estado. Almacena en la memoria todos los datos que se escriban en él, por lo que se pierden todos los datos cuando se apaga. Si estás ejecutando varias pruebas con el mismo ID de proyecto, cada prueba puede producir datos que podrían influir en las pruebas posteriores. Para evitar este comportamiento, puedes usar cualquiera de los siguientes métodos:

  • Usa ID de proyecto únicos para cada prueba. Ten en cuenta que, si decides hacerlo, deberás llamar a initializeTestEnvironment como parte de cada prueba. Las reglas se cargarán automáticamente solo para el ID del proyecto predeterminado.
  • Reestructura tus pruebas, de modo que no interactúen con datos escritos previamente (por ejemplo, usa una colección diferente para cada prueba).
  • Borra todos los datos escritos durante una prueba.

La configuración de la prueba es muy complicada

Cuando configures la prueba, es posible que quieras modificar los datos de una manera que las reglas de seguridad de Firestore no permitan. Si las reglas hacen que la configuración de la prueba sea compleja, intenta usar RulesTestEnvironment.withSecurityRulesDisabled en los pasos de configuración, de manera que las operaciones de lectura y escritura no activarán errores PERMISSION_DENIED.

Después de eso, la prueba puede realizar operaciones como usuarios autenticados o no autenticados mediante RulesTestEnvironment.authenticatedContext y unauthenticatedContext, respectivamente. Esto te permite validar que tus reglas de seguridad de Firestore permitan o rechacen diferentes casos de manera correcta.