Teste suas regras de segurança do Cloud Firestore

Ao criar seu aplicativo, talvez você queira bloquear o acesso ao banco de dados do Cloud Firestore. No entanto, antes do lançamento, você precisará de regras de segurança do Cloud Firestore com mais nuances. Com o emulador do Cloud Firestore, além de criar protótipos e testar os recursos e o comportamento geral do seu aplicativo, você pode escrever testes de unidade que verificam o comportamento das regras de segurança do Cloud Firestore.

Começo rápido

Para alguns casos de teste básicos com regras simples, experimente o exemplo de início rápido .

Entenda as regras de segurança do Cloud Firestore

Implemente regras de segurança do Firebase Authentication e Cloud Firestore para autenticação sem servidor, autorização e validação de dados ao usar as bibliotecas de cliente móvel e web.

As regras de segurança do Cloud Firestore incluem duas partes:

  1. Uma instrução match que identifica documentos no seu banco de dados.
  2. Uma expressão allow que controla o acesso a esses documentos.

O Firebase Authentication verifica as credenciais dos usuários e fornece a base para sistemas de acesso baseados em usuários e em funções.

Cada solicitação de banco de dados de uma biblioteca cliente móvel/web do Cloud Firestore é avaliada em relação às suas regras de segurança antes de ler ou gravar quaisquer dados. Se as regras negarem acesso a qualquer um dos caminhos de documento especificados, toda a solicitação falhará.

Saiba mais sobre as regras de segurança do Cloud Firestore em Primeiros passos com as regras de segurança do Cloud Firestore .

Instale o emulador

Para instalar o emulador do Cloud Firestore, use a CLI do Firebase e execute o comando abaixo:

firebase setup:emulators:firestore

Execute o emulador

Comece inicializando um projeto do Firebase em seu diretório de trabalho. Esta é uma primeira etapa comum ao usar a CLI do Firebase .

firebase init

Inicie o emulador usando o seguinte comando. O emulador será executado até você encerrar o processo:

firebase emulators:start --only firestore

Em muitos casos, você deseja iniciar o emulador, executar um conjunto de testes e, em seguida, encerrar o emulador após a execução dos testes. Você pode fazer isso facilmente usando o comando emulators:exec :

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

Quando iniciado, o emulador tentará rodar em uma porta padrão (8080). Você pode alterar a porta do emulador modificando a seção "emulators" do arquivo firebase.json :

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

Antes de executar o emulador

Antes de começar a usar o emulador, lembre-se do seguinte:

  • O emulador carregará inicialmente as regras especificadas no campo firestore.rules do seu arquivo firebase.json . Ele espera o nome de um arquivo local contendo suas regras de segurança do Cloud Firestore e aplica essas regras a todos os projetos. Se você não fornecer o caminho do arquivo local ou usar o método loadFirestoreRules conforme descrito abaixo, o emulador tratará todos os projetos como tendo regras abertas.
  • Embora a maioria dos SDKs do Firebase funcionem diretamente com os emuladores, apenas a biblioteca @firebase/rules-unit-testing oferece suporte à simulação de auth nas regras de segurança, tornando os testes de unidade muito mais fáceis. Além disso, a biblioteca oferece suporte a alguns recursos específicos do emulador, como limpar todos os dados, conforme listado abaixo.
  • Os emuladores também aceitarão tokens de produção do Firebase Auth fornecidos por meio de SDKs de cliente e avaliarão as regras de acordo, o que permite conectar seu aplicativo diretamente aos emuladores em integração e testes manuais.

Execute testes de unidade local

Execute testes de unidade local com o SDK JavaScript v9

O Firebase distribui uma biblioteca de teste de unidade de regras de segurança com seu SDK JavaScript versão 9 e seu SDK versão 8. As APIs da biblioteca são significativamente diferentes. Recomendamos a biblioteca de testes v9, que é mais simplificada e requer menos configuração para conectar-se a emuladores e, assim, evitar com segurança o uso acidental de recursos de produção. Para compatibilidade com versões anteriores, continuamos disponibilizando a biblioteca de testes v8 .

Use o módulo @firebase/rules-unit-testing para interagir com o emulador executado localmente. Se você obtiver tempos limite ou erros ECONNREFUSED , verifique novamente se o emulador está realmente em execução.

Recomendamos fortemente o uso de uma versão recente do Node.js para que você possa usar a notação async/await . Quase todo o comportamento que você deseja testar envolve funções assíncronas, e o módulo de teste foi projetado para funcionar com código baseado em Promise.

A biblioteca de testes unitários de regras v9 está sempre ciente dos emuladores e nunca afeta seus recursos de produção.

Você importa a biblioteca usando instruções de importação modular v9. Por exemplo:

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.

Depois de importado, a implementação de testes unitários envolve:

  • Criando e configurando um RulesTestEnvironment com uma chamada para initializeTestEnvironment .
  • Configurar dados de teste sem acionar regras, usando um método conveniente que permite ignorá-los temporariamente, RulesTestEnvironment.withSecurityRulesDisabled .
  • Configurando o conjunto de testes e ganchos antes/depois por teste com chamadas para limpar dados e ambiente de teste, como RulesTestEnvironment.cleanup() ou RulesTestEnvironment.clearFirestore() .
  • Implementar casos de teste que imitam estados de autenticação usando RulesTestEnvironment.authenticatedContext e RulesTestEnvironment.unauthenticatedContext .

Métodos comuns e funções utilitárias

Consulte também métodos de teste específicos do emulador no SDK v9 .

initializeTestEnvironment() => RulesTestEnvironment

Esta função inicializa um ambiente de teste para testes unitários de regras. Chame esta função primeiro para configuração de teste. A execução bem-sucedida requer que os emuladores estejam em execução.

A função aceita um objeto opcional que define um TestEnvironmentConfig , que pode consistir em um ID de projeto e definições de configuração do 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 cria um RulesTestContext , que se comporta como um usuário de autenticação autenticado. As solicitações criadas por meio do contexto retornado terão um token de autenticação simulado anexado. Opcionalmente, passe um objeto que defina declarações ou substituições personalizadas para cargas úteis de token de autenticação.

Use o objeto de contexto de teste retornado em seus testes para acessar quaisquer instâncias de emulador configuradas, incluindo aquelas configuradas com 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 cria um RulesTestContext , que se comporta como um cliente que não está logado via autenticação. As solicitações criadas por meio do contexto retornado não terão tokens do Firebase Auth anexados.

Use o objeto de contexto de teste retornado em seus testes para acessar quaisquer instâncias de emulador configuradas, incluindo aquelas configuradas com 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()

Execute uma função de configuração de teste com um contexto que se comporta como se as regras de segurança estivessem desabilitadas.

Este método usa uma função de retorno de chamada, que pega o contexto de desvio de regras de segurança e retorna uma promessa. O contexto será destruído assim que a promessa for resolvida/rejeitada.

RulesTestEnvironment.cleanup()

Este método destrói todos os RulesTestContexts criados no ambiente de teste e limpa os recursos subjacentes, permitindo uma saída limpa.

Este método não altera o estado dos emuladores de forma alguma. Para redefinir dados entre testes, use o método de limpeza de dados específico do emulador do aplicativo.

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

Esta é uma função utilitária de caso de teste.

A função afirma que a operação Promise fornecida envolvendo um emulador será resolvida sem violações das regras de segurança.

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

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

Esta é uma função utilitária de caso de teste.

A função afirma que a promessa fornecida envolvendo uma operação de emulador será rejeitada com uma violação das regras de segurança.

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

Métodos específicos do emulador

Consulte também métodos de teste comuns e funções utilitárias no SDK v9 .

RulesTestEnvironment.clearFirestore() => Promise<void>

Este método limpa os dados no banco de dados do Firestore que pertencem ao projectId configurado para o emulador do Firestore.

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

Este método obtém uma instância do Firestore para este contexto de teste. A instância do Firebase JS Client SDK retornada pode ser usada com as APIs do SDK do cliente (v9 modular ou v9 compatível).

Visualize avaliações de regras

O emulador do Cloud Firestore permite visualizar solicitações de clientes na IU do Emulator Suite, incluindo rastreamento de avaliação para regras de segurança do Firebase.

Abra a guia Firestore > Solicitações para visualizar a sequência de avaliação detalhada de cada solicitação.

Monitor de solicitações do emulador do Firestore mostrando avaliações de regras de segurança

Gerar relatórios de teste

Depois de executar um conjunto de testes, você poderá acessar relatórios de cobertura de testes que mostram como cada uma de suas regras de segurança foi avaliada.

Para obter os relatórios, consulte um ponto de extremidade exposto no emulador enquanto ele estiver em execução. Para uma versão amigável ao navegador, use o seguinte URL:

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

Isso divide suas regras em expressões e subexpressões que você pode passar o mouse para obter mais informações, incluindo o número de avaliações e valores retornados. Para a versão JSON bruta desses dados, inclua o seguinte URL na sua consulta:

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

Diferenças entre o emulador e a produção

  1. Você não precisa criar explicitamente um projeto do Cloud Firestore. O emulador cria automaticamente qualquer instância acessada.
  2. O emulador do Cloud Firestore não funciona com o fluxo normal do Firebase Authentication. Em vez disso, no Firebase Test SDK, fornecemos o método initializeTestApp() na biblioteca rules-unit-testing , que usa um campo auth . O identificador do Firebase criado usando esse método se comportará como se tivesse sido autenticado com sucesso como qualquer entidade que você fornecer. Se você passar null , ele se comportará como um usuário não autenticado ( auth != null regras falharão, por exemplo).

Solucionar problemas conhecidos

Ao usar o emulador do Cloud Firestore, você poderá encontrar os seguintes problemas conhecidos. Siga as orientações abaixo para solucionar qualquer comportamento irregular que você esteja enfrentando. Estas notas foram escritas tendo em mente a biblioteca de testes unitários de regras de segurança, mas as abordagens gerais são aplicáveis ​​a qualquer SDK do Firebase.

O comportamento do teste é inconsistente

Se seus testes forem aprovados e reprovados ocasionalmente, mesmo sem nenhuma alteração nos testes em si, talvez seja necessário verificar se eles estão sequenciados corretamente. A maioria das interações com o emulador são assíncronas, portanto, verifique novamente se todo o código assíncrono está sequenciado corretamente. Você pode corrigir o sequenciamento encadeando promessas ou usando a notação await liberalmente.

Em particular, revise as seguintes operações assíncronas:

  • Definir regras de segurança, por exemplo, initializeTestEnvironment .
  • Lendo e gravando dados, com, por exemplo, db.collection("users").doc("alice").get() .
  • Asserções operacionais, incluindo assertSucceeds e assertFails .

Os testes só passam na primeira vez que você carrega o emulador

O emulador tem estado. Ele armazena todos os dados gravados na memória, portanto, todos os dados são perdidos sempre que o emulador é desligado. Se você estiver executando vários testes no mesmo ID de projeto, cada teste poderá produzir dados que podem influenciar os testes subsequentes. Você pode usar qualquer um dos seguintes métodos para ignorar esse comportamento:

  • Use IDs de projeto exclusivos para cada teste. Observe que se você optar por fazer isso, precisará chamar initializeTestEnvironment como parte de cada teste; as regras são carregadas automaticamente apenas para o ID do projeto padrão.
  • Reestruture seus testes para que não interajam com dados gravados anteriormente (por exemplo, use uma coleção diferente para cada teste).
  • Exclua todos os dados gravados durante um teste.

A configuração do teste é muito complicada

Ao configurar seu teste, você pode modificar os dados de uma forma que as regras de segurança do Cloud Firestore não permitem. Se suas regras estão tornando a configuração do teste complexa, tente usar RulesTestEnvironment.withSecurityRulesDisabled nas etapas de configuração, para que leituras e gravações não acionem erros PERMISSION_DENIED .

Depois disso, seu teste pode executar operações como um usuário autenticado ou não autenticado usando RulesTestEnvironment.authenticatedContext e unauthenticatedContext respectivamente. Isso permite que você valide se suas regras de segurança do Cloud Firestore permitem/negam diferentes casos corretamente.