Como funcionam as regras de segurança do Firebase na Realtime Database?
Parte 4 — Testes Unitários
Na parte 3 desta série de artigos mostrei como utilizar o Simulador de Regras da Realtime Database. O simulador é uma ferramenta muito útil e que ajuda bastante na hora de testar as nossas regras online.
Mas hoje, durante o Firebase Summit 2018, foram lançadas 2 novas ferramentas que ajudam também na hora de testar regras: o Emulador da Realtime Database e o módulo firebase-testing da Firebase CLI. Neste artigo mostro como funcionam estas ferramentas e como elas facilitam-nos quando queremos verificar o comportamento das nossas regras de segurança.
Realtime Database Emulator
O emulador da Realtime Database é uma ferramenta que permite simular uma instância de uma database na nossa máquina local. Todas as operações que executamos na nossa Realtime Database podem ser simuladas localmente no emulador. A única diferença é que o emulador não está interligado aos outros serviços do Firebase.
Instalação
Para instalar o emulador, temos de executar os seguintes comandos:
firebase --open-sesame emulators
firebase setup:emulators:database
Iniciar
Agora que temos o emulador instalado, podemos iniciá-lo usando o comando: firebase serve --only firestore
. Ao introduzir o comando, deverá aparecer uma mensagem dizendo “Listening on port 9000” no seu ecrã.
Geralmente a Realtime Database pode ser acedida através da URL https://<nome-da-database>.firebaseio.com/caminho/dos/dados.json
. Ao iniciar o emulador, a mesma database pode ser acedida também pela URL http://localhost:9000/caminho/dos/dados.json?ns=<nome-da-database>
.
Quando você inicia o emulador, a realtime database simulada é criada com as regras privadas (.write
e .read
em false
).
Módulo node.js firebase-testing
Este módulo permite-nos escrever testes unitários para a nossa database. Neste artigo, vamos criar um diretório chamado teste
, e neste diretório vamos instalar o nosso módulo firebase-testing
. Para isso, basta utilizarmos o comando:
npm install --save @firebase/testing
async/await
Antes de começarmos a escrever os testes unitários, há 2 conceitos que podem ser novos para você: async
e await
.
Você já deve saber que as funções de leitura e escrita do Firebase funcionam de forma assíncrona. Mas como os nossos testes serão executados em sequência, não podemos fazê-los de forma assíncrona, pois a sequência se perderá.
E para nos ajudar com isso, o JavaScript possui o async
e o await
que permitem-nos esperar pelo resultado de uma operação antes de ir para a próxima. Por exemplo, para ler um utilizador de forma assíncrona no JavaScript, você faria:
function lerUtilizador(uid)
{
rootRef.child("utilizadores").child(uid).once('value', function(snapshot){
var utilizador = snapshot.val();
//Mostrar o utilizador
});
}
Para fazer a mesma leitura, de forma síncrona, você deve fazer:
async function lerUtilizador(uid)
{
var snapshot = await rootRef.child("utilizadores").child(uid).once('value');
var utilizador = snapshot.val();
//Mostrar o utilizador
}
Há uma coisa a notar: o async
serve para anotar funções e o await
só pode ser utilizado em funções que foram declaradas com a anotação async
.
Você pode aprender mais sobre async e await neste post do Jose Pedro Dava.
Escrever Testes Unitários
Para escrever os nossos testes, o firebase-testing possui as funções:
initializeTestApp({ databaseName, auth })
— simula uma app que está a ser acedida por um utilizador autenticado, que é especificado no parâmetroauth
.initializeAdminApp({ databaseName })
— simula uma app que está ser acedida com privilégios de administrador (é como usar o Admin SDK ou a REST API para aceder à database).loadRules({ databaseName , rulesPath })
— carrega as regras de segurança que iremos testar.apps()
— retorna a lista de todos as apps que foram inicializadas para simulação.assertFails(pr: Promise)
— verifica se uma escrita/leitura irá falhar.assertSucceeds(pr: Promise)
— verifica se uma escrita/leitura irá ser executada com sucesso.
Agora que conhecemos as funções, podemos então escrever testes unitários para as regras (veja database.rules.json) da database que temos usado durante esta série de artigos (veja MyDatabase.json).
Por exemplo, uma regra que definimos foi: “Utilizadores só podem enviar mensagens para grupos que fazem parte”. A regra escrita foi:
E o código JavaScript para enviar a mensagem seria algo como:
rootRef.child(“mensagens/g1”).push().set(“Olá Mundo!”);
Então usamos este mesmo código simulando um utilizador autenticado. Podemos verificar se o utilizador de uid=”uid1"
pode realmente escrever no grupo com a chave “g1” (que ele faz parte):
let firebase = require("@firebase/testing");let app = firebase.initializeTestApp({ databaseName: "MinhaDB", auth: {uid:"uid1"} });await firebase.assertSucceeds(app.database().ref("mensagens/g1").push().set("Olá Mundo"));
E podemos nos certificar que ele não tem acesso ao grupo “g2”:
await firebase.assertFails(app.database().ref("mensagens/g2").push().set("Olá Mundo"));
Juntando os dois, o nosso ficheiro index.js
(no directório teste
) ficaria assim:
Este é um dos exemplos mais simples. Completei o ficheiro index.js
para testar todas as regras que foram definidas, e ficou assim:
Depois de escrever os testes, temos de executá-los. Para isso, voltamos à terminal e (no diretório parente do diretório teste) executamos o comando npm test teste
. Você deverá ter um output semelhante à este:
E por hoje é tudo. Espero que você tenha compreendido e que comece também a escrever testes unitários para a sua database. ;)
Caso tenha alguma dúvida ou sugestão, deixe abaixo nos comentários. Se você estiver tentando escrever testes unitários e teve um problema, coloque ele no StackOverflow, mostrando o que você fez e qual foi o erro que teve. De certeza que você obterá ajuda de mim ou de alguém da comunidade. 🙂