Testowanie reguł zabezpieczeń Cloud Firestore

Podczas tworzenia aplikacji możesz zablokować dostęp do bazy danych Cloud Firestore. Przed uruchomieniem potrzebujesz bardziej szczegółowych reguł zabezpieczeń Cloud Firestore. Emulator Cloud Firestore pozwala nie tylko tworzyć prototypy i testować ogólne funkcje oraz działanie aplikacji, ale też pisać testy jednostkowe, które sprawdzają działanie reguł zabezpieczeń Cloud Firestore.

Krótkie wprowadzenie

Kilka podstawowych zastosowań testowych z prostymi regułami znajdziesz w krótkim wprowadzeniu.

Omówienie reguł zabezpieczeń Cloud Firestore

Gdy używasz bibliotek klienta mobilnego i internetowego, zaimplementuj Uwierzytelnianie Firebase i reguły zabezpieczeń Cloud Firestore, aby zapewnić bezserwerowe uwierzytelnianie, autoryzację i weryfikację danych.

Reguły zabezpieczeń Cloud Firestore składają się z 2 części:

  1. Instrukcja match identyfikująca dokumenty w bazie danych.
  2. Wyrażenie allow kontrolujące dostęp do tych dokumentów.

Uwierzytelnianie Firebase weryfikuje dane logowania użytkowników oraz umożliwia tworzenie podstaw dla systemów dostępu opartych na użytkownikach i rolach.

Przed odczytaniem lub zapisem danych każde żądanie bazy danych z biblioteki klienta mobilnego/internetowego Cloud Firestore jest oceniane pod kątem reguł zabezpieczeń. Jeśli reguły odmówią dostępu do dowolnej z podanych ścieżek dokumentów, całe żądanie zakończy się niepowodzeniem.

Więcej informacji o regułach zabezpieczeń Cloud Firestore znajdziesz w artykule Pierwsze kroki z regułami zabezpieczeń Cloud Firestore.

Zainstaluj emulator

Aby zainstalować emulator Cloud Firestore, użyj interfejsu wiersza poleceń Firebase i uruchom to polecenie:

firebase setup:emulators:firestore

Uruchamianie emulatora

Zacznij od zainicjowania projektu Firebase w katalogu roboczym. Jest to typowy pierwszy krok podczas korzystania z interfejsu wiersza poleceń Firebase.

firebase init

Uruchom emulator za pomocą tego polecenia. Emulator będzie działać, dopóki nie zakończysz procesu:

firebase emulators:start --only firestore

Często trzeba uruchomić emulator, uruchomić pakiet testów, a potem wyłączyć emulator po przeprowadzeniu testów. Możesz to łatwo zrobić za pomocą polecenia emulators:exec:

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

Po uruchomieniu emulator będzie próbował uruchomić się na porcie domyślnym (8080). Możesz zmienić port emulatora, modyfikując sekcję "emulators" pliku firebase.json:

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

Zanim uruchomisz emulator

Zanim zaczniesz korzystać z emulatora, pamiętaj o tych kwestiach:

  • Emulator początkowo wczyta reguły określone w polu firestore.rules pliku firebase.json. Oczekuje ona nazwy lokalnego pliku zawierającego reguły zabezpieczeń Cloud Firestore i stosuje te reguły do wszystkich projektów. Jeśli nie podasz lokalnej ścieżki pliku lub użyjesz metody loadFirestoreRules w sposób opisany poniżej, emulator potraktuje wszystkie projekty jako mające otwarte reguły.
  • Większość pakietów SDK Firebase współpracuje bezpośrednio z emulatorami, ale tylko biblioteka @firebase/rules-unit-testing obsługuje w regułach zabezpieczeń żarty z auth, co znacznie ułatwia testowanie jednostkowe. Dodatkowo biblioteka obsługuje kilka funkcji specyficznych dla emulatorów, takich jak czyszczenie wszystkich danych wymienionych poniżej.
  • Emulatory będą też akceptować produkcyjne tokeny uwierzytelniania Firebase dostarczane za pomocą pakietów SDK klienta i odpowiednio oceniać reguły, co umożliwia bezpośrednie połączenie aplikacji z emulatorami w ramach testów integracji i testów ręcznych.

Przeprowadzanie lokalnych testów jednostkowych

Przeprowadzanie lokalnych testów jednostkowych za pomocą pakietu SDK JavaScript w wersji 9

Firebase rozpowszechnia bibliotekę testowania jednostkową reguł zabezpieczeń wraz z pakietem SDK JavaScript w wersji 9 oraz pakietem SDK w wersji 8. Interfejsy API bibliotek znacznie się różnią. Zalecamy korzystanie z biblioteki testowej w wersji 9, która jest bardziej usprawniona i wymaga mniej konfiguracji do łączenia się z emulatorami, dzięki czemu można bezpiecznie unikać przypadkowego użycia zasobów produkcyjnych. Aby zapewnić zgodność wsteczną, nadal udostępniamy bibliotekę do testowania wersji 8.

Wykorzystaj moduł @firebase/rules-unit-testing do interakcji z emulatorem, który działa lokalnie. Jeśli pojawią się błędy ECONNREFUSED lub przekroczenie czasu oczekiwania, sprawdź, czy emulator faktycznie działa.

Zdecydowanie zalecamy korzystanie z najnowszej wersji środowiska Node.js, co umożliwi korzystanie z notacji async/await. Prawie wszystkie działanie, które warto przetestować, obejmuje funkcje asynchroniczne, a moduł testowania został zaprojektowany do współpracy z kodem opartym na obietnicy.

Biblioteka testowania jednostkowego reguł w wersji 9 zawsze wie o emulatorach i nigdy nie ma wpływu na zasoby produkcyjne.

Bibliotekę zaimportujesz za pomocą modułowych instrukcji importowania w wersji 9. Przykład:

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.

Po zaimportowaniu testy jednostkowe obejmują:

  • Tworzę i konfiguruję RulesTestEnvironment z wywołaniem initializeTestEnvironment.
  • Skonfigurowanie danych testowych bez uruchamiania reguł za pomocą wygodnej metody, która umożliwia tymczasowe ich ominięcie RulesTestEnvironment.withSecurityRulesDisabled.
  • Konfigurowanie pakietu testowego i punktów zaczepienia przed i po każdym teście z wywołaniami do oczyszczania danych i środowiska testowego, np. RulesTestEnvironment.cleanup() lub RulesTestEnvironment.clearFirestore().
  • Wdrażanie przypadków testowych imitujących stany uwierzytelniania za pomocą RulesTestEnvironment.authenticatedContext i RulesTestEnvironment.unauthenticatedContext.

Typowe metody i funkcje użytkowe

Zobacz też metody testowe związane z emulatorami w pakiecie SDK w wersji 9.

initializeTestEnvironment() => RulesTestEnvironment

Ta funkcja inicjuje środowisko testowe do testowania jednostkowego reguł. Wywołaj tę funkcję najpierw na potrzeby konfiguracji testu. Udane wykonanie wymaga uruchomienia emulatorów.

Funkcja ta akceptuje opcjonalny obiekt definiujący TestEnvironmentConfig, który może składać się z identyfikatora projektu i ustawień konfiguracji emulatora.

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

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

Ta metoda tworzy obiekt RulesTestContext, który działa jak uwierzytelniony użytkownik uwierzytelniania. Do żądań utworzonych przy użyciu zwróconego kontekstu zostanie dołączony przykładowy token uwierzytelniania. Opcjonalnie możesz też przekazać obiekt definiujący deklaracje niestandardowe lub zastąpienia dla ładunków tokenów uwierzytelniania.

Aby uzyskać dostęp do wszystkich skonfigurowanych instancji emulatora, w tym tych skonfigurowanych przy użyciu initializeTestEnvironment, użyj w testach zwróconego obiektu kontekstu testowego.

// 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

Ta metoda tworzy RulesTestContext, który działa jak klient, który nie jest zalogowany za pomocą uwierzytelniania. Do żądań utworzonych przy użyciu zwróconego kontekstu nie będą dołączone tokeny uwierzytelniania Firebase.

Aby uzyskać dostęp do wszystkich skonfigurowanych instancji emulatora, w tym tych skonfigurowanych przy użyciu initializeTestEnvironment, użyj w testach zwróconego obiektu kontekstu testowego.

// 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()

Uruchom funkcję konfiguracji testu w kontekście, który działa tak, jakby reguły zabezpieczeń były wyłączone.

Ta metoda korzysta z funkcji wywołania zwrotnego, która wykorzystuje kontekst pomijania reguł zabezpieczeń i zwraca obietnicę. Gdy obietnica zostanie spełniona lub odrzucona, kontekst zostanie zniszczony.

RulesTestEnvironment.cleanup()

Ta metoda niszczy wszystkie RulesTestContexts utworzone w środowisku testowym i czyści bazowe zasoby, umożliwiając czyszczenie wyjścia.

Ta metoda nie zmienia w żaden sposób stanu emulatorów. Aby resetować dane między testami, użyj metody czyszczenia danych specyficznej dla emulatora aplikacji.

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

To jest funkcja narzędziowa przypadku testowego.

Ta funkcja zapewnia, że podana operacja zawijania obietnic z emulatora zostanie zakończona bez naruszeń reguł zabezpieczeń.

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

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

To jest funkcja narzędziowa przypadku testowego.

Ta funkcja zapewnia, że dostarczone działanie emulatora obietnicowe zostanie odrzucone z powodu naruszenia reguł zabezpieczeń.

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

Metody specyficzne dla emulatora

Zapoznaj się też z typowymi metodami testowania i funkcjami narzędziowymi w pakiecie SDK w wersji 9.

RulesTestEnvironment.clearFirestore() => Promise<void>

Ta metoda czyści w bazie danych Firestore dane należące do projectId skonfigurowanego dla emulatora Firestore.

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

Ta metoda pobiera instancję Firestore na potrzeby tego kontekstu testowego. Zwróconej instancji pakietu SDK Firebase JS można używać z interfejsami API pakietu SDK klienta (modułowym w wersji 9 lub zgodnym z wersją 9).

Wizualizacja ocen reguł

Emulator Cloud Firestore umożliwia wizualizację żądań klientów w interfejsie pakietu emulatorów, w tym śledzenie ocen przez reguły zabezpieczeń Firebase.

Otwórz kartę Firestore > Żądania, aby wyświetlić szczegółową sekwencję oceny każdego żądania.

Monitor żądań emulatora Firestore pokazujący oceny reguł zabezpieczeń

Generowanie raportów z testów

Po przeprowadzeniu zestawu testów możesz uzyskać dostęp do raportów o stanie testów, które pokazują, jak została oceniona każda z Twoich reguł zabezpieczeń.

Aby uzyskać te raporty, wyślij w emulatorze zapytanie do ujawnionego punktu końcowego, gdy jest on uruchomiony. W przypadku wersji przeznaczonej do wyświetlania w przeglądarce użyj tego adresu URL:

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

Spowoduje to podział reguł na wyrażenia i wyrażenia podrzędne, po których możesz najechać na nie kursorem, aby uzyskać więcej informacji, w tym liczbę ocen i zwróconych wartości. W przypadku nieprzetworzonej wersji JSON tych danych uwzględnij w zapytaniu ten adres URL:

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

Różnice między emulatorem a produkcją

  1. Nie musisz bezpośrednio tworzyć projektu Cloud Firestore. Emulator automatycznie tworzy każdą instancję, do której uzyskano dostęp.
  2. Emulator Cloud Firestore nie działa z zwykłym przepływem uwierzytelniania Firebase. Zamiast tego w pakiecie SDK Firebase Test SDK w bibliotece rules-unit-testing udostępniliśmy metodę initializeTestApp(), która zajmuje pole auth. Utworzony przy użyciu tej metody uchwyt Firebase będzie działać tak, jakby został pomyślnie uwierzytelniony jako dowolna podana przez Ciebie encja. Jeśli przekażesz zasadę null, będzie ona zachowywać się jak użytkownik nieuwierzytelniony (np. reguły auth != null zakończą się niepowodzeniem).

Rozwiązywanie znanych problemów

Podczas korzystania z emulatora Cloud Firestore mogą wystąpić poniższe znane problemy. Aby rozwiązać problemy z nieregularnym zachowaniem, postępuj zgodnie z poniższymi wskazówkami. Zostały one pisane z myślą o bibliotece testowania jednostkowego reguł zabezpieczeń, ale ogólne podejścia mają zastosowanie do każdego pakietu SDK Firebase.

Działanie testu jest niespójne

Jeśli testy zaliczają się od czasu do czasu i kończą się niepowodzeniem, nawet jeśli nie wprowadzasz w nich żadnych zmian, sprawdź, czy są ustawione prawidłowo sekwencja. Większość interakcji z emulatorem ma charakter asynchroniczny, więc upewnij się, że cały kod asynchroniczny jest poprawnie ułożony w kolejności. Możesz poprawić sekwencjonowanie, łącząc obietnice w łańcuchu lub swobodnie używając zapisu await.

W szczególności sprawdź te operacje asynchroniczne:

  • Ustawianie reguł zabezpieczeń, na przykład initializeTestEnvironment.
  • Odczyt i zapis danych, np. db.collection("users").doc("alice").get().
  • Potwierdzenia działań, w tym assertSucceeds i assertFails.

Testy zaliczają się tylko przy pierwszym wczytaniu emulatora

Emulator jest stanowy. Wszystkie zapisane w nim dane są przechowywane w pamięci, więc po wyłączeniu emulatora wszystkie dane są usuwane. Jeśli wykonujesz kilka testów z tym samym identyfikatorem projektu, każdy z nich może dostarczyć dane, które mogą mieć wpływ na kolejne testy. Aby obejść to zachowanie, możesz użyć dowolnej z tych metod:

  • W każdym teście używaj unikalnych identyfikatorów projektów. Pamiętaj, że jeśli to zrobisz, w ramach każdego testu musisz wywołać metodę initializeTestEnvironment. Reguły będą ładowane automatycznie tylko w przypadku domyślnego identyfikatora projektu.
  • Zmień strukturę testów w taki sposób, aby nie wchodziły w interakcję z wcześniej zapisanymi danymi (np. użyj innej kolekcji w przypadku każdego testu).
  • Usuń wszystkie dane zapisane podczas testu.

Konfiguracja testu jest bardzo skomplikowana

Podczas konfigurowania testu warto zmodyfikować dane w sposób, na który reguły zabezpieczeń Cloud Firestore nie zezwalają. Jeśli Twoje reguły sprawiają, że konfiguracja testu jest skomplikowana, spróbuj użyć w krokach konfiguracji funkcji RulesTestEnvironment.withSecurityRulesDisabled, aby odczyty i zapisy nie wywołały błędów PERMISSION_DENIED.

Po tym czasie test może wykonywać operacje jako uwierzytelniony lub nieuwierzytelniony użytkownik odpowiednio przy użyciu RulesTestEnvironment.authenticatedContext i unauthenticatedContext. Pozwala to sprawdzić, czy reguły zabezpieczeń Cloud Firestore prawidłowo zezwalają na poszczególne przypadki lub ich nie zezwalają.