Prueba las reglas de seguridad de Cloud Firestore

Recomendamos que bloquees el acceso a la base de datos de Cloud Firestore mientras compilas la app. Sin embargo, antes de que la inicies, deberás contar con Cloud Firestore Security Rules más detalladas. Con el emulador Cloud Firestore, además de prototipar y probar las funciones y el comportamiento en general de tu app, puedes escribir pruebas de unidades que verifiquen el comportamiento de tus Cloud Firestore Security Rules.

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 Cloud Firestore Security Rules

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

Las Cloud Firestore Security Rules 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 Cloud Firestore web o para dispositivos móviles se comparan con tus reglas de seguridad antes de poder leer o escribir datos. Si las reglas rechazan el acceso a alguna de las rutas de los documentos especificados, falla la solicitud completa.

Obtén más información sobre Cloud Firestore Security Rules en Cómo comenzar a usar Cloud Firestore Security Rules.

Instala el emulador

Para instalar el emulador de Cloud Firestore, usa la CLI de Firebase y ejecuta el siguiente comando:

firebase setup:emulators:firestore

Ejecuta el emulador

Comienza por inicializar un proyecto de Firebase en el directorio de trabajo. Este es un primer paso habitual cuando se usa 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 de tu archivo firebase.json. Esta espera el nombre de un archivo local con tus Cloud Firestore Security Rules 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.

Ejecuta pruebas de unidades locales

Ejecuta pruebas de unidades locales con el SDK de JavaScript de la versión 9

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 APIs de la biblioteca son muy diferentes. Recomendamos la biblioteca de prueba de la versión 9, que es más optimizada y requiere menos configuración para conectarse a los emuladores y, por lo tanto, evitar de forma segura el uso accidental de recursos de producción. Para la retrocompatibilidad, seguimos ofreciendo la biblioteca de pruebas de la versión 8.

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 código basado en promesas.

La biblioteca de pruebas de unidades de reglas de la versión 9 siempre tiene en cuenta los emuladores y nunca toca tus recursos de producción.

Importarás la biblioteca usando las instrucciones de importación de la versión 9 modular. Por ejemplo:

import {
  assertFails,
  assertSucceeds,
  initializeTestEnvironment
} 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.

Cuando esté importada, la implementación de pruebas de unidades implica lo siguiente:

  • Crear y configurar un 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 paquetes de pruebas y hooks por prueba antes o después con llamadas para limpiar los datos y el entorno de prueba, como RulesTestEnvironment.cleanup() o RulesTestEnvironment.clearFirestore()
  • Implementar casos de prueba que imiten estados de autenticación mediante RulesTestEnvironment.authenticatedContext y RulesTestEnvironment.unauthenticatedContext

Métodos y funciones de utilidad comunes

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

initializeTestEnvironment() => RulesTestEnvironment

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

La función acepta un objeto opcional que define una TestEnvironmentConfig, que puede consistir en un ID del proyecto y en parámetros 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 autenticado de Authentication. 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 Authentication.

Usa en tus pruebas el objeto de contexto de prueba que se muestra 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ó mediante Authentication. Las solicitudes creadas a través del contexto mostrado no tendrán tokens de Firebase Auth adjuntos.

Usa en tus pruebas el objeto de contexto de prueba que se muestra 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 usa 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á cuando 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 aplicación.

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 de 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 de la versión 9.

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 cliente de Firebase JS que se muestra se puede usar con las API del SDK cliente (compatibles con la versión 9 modular o la versión 9).

Visualiza las evaluaciones de las reglas

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

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

El monitor de solicitudes del emulador de Firestore, que muestra las 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 ejecuta. 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 la producción

  1. No tienes que crear un proyecto de Cloud Firestore de manera explícita. El emulador crea automáticamente cualquier instancia a la que se acceda.
  2. El emulador de Cloud 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 Cloud Firestore, podrías tener los siguientes problemas conocidos. Sigue las recomendaciones de más abajo para solucionar cualquier problema de comportamiento irregular que experimentes. Estas notas se escribieron para 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 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, te recomendamos modificar los datos de tal manera que las Cloud Firestore Security Rules no los 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 esa forma, las operaciones de lectura y escritura no activarán errores PERMISSION_DENIED.

Luego de eso, la prueba puede realizar operaciones como usuarios autenticados o no autenticados mediante RulesTestEnvironment.authenticatedContext y unauthenticatedContext, respectivamente. Esto te permite validar si tus Cloud Firestore Security Rules permiten o rechazan diferentes casos correctamente.