Testowanie reguł zabezpieczeń Cloud Firestore

Podczas tworzenia aplikacji możesz chcieć zablokować dostęp do bazy danych Cloud Firestore. Zanim jednak to zrobisz, musisz dowiedzieć się więcej o tym, jak Cloud Firestore Security Rules. W emulatorze Cloud Firestore możesz nie tylko tworzyć prototypy i testować ogólne funkcje i zachowanie aplikacji, ale też pisać testy jednostkowe, które sprawdzają zachowanie Cloud Firestore Security Rules.

Krótkie wprowadzenie

Aby zobaczyć kilka podstawowych przypadków testowych z prostymi regułami, skorzystaj z przykładu szybkiego startu.

Cloud Firestore Security Rules

W przypadku korzystania z bibliotek klienta na urządzenia mobilne i w internecie należy zaimplementować Firebase AuthenticationCloud Firestore Security Rules do uwierzytelniania bez serwera, autoryzacji i weryfikacji danych.

Cloud Firestore Security Rules składają się z 2 elementów:

  1. match, który identyfikuje dokumenty w Twojej bazie danych.
  2. allow wyrażenie kontrolujące dostęp do tych dokumentów.

Firebase Authentication weryfikuje dane logowania użytkowników i stanowi podstawę systemów dostępu opartych na użytkownikach i rolach.

Każde żądanie bazy danych z biblioteki klienta mobilnego/internetowego Cloud Firestore jest sprawdzane pod kątem zgodności z regułami zabezpieczeń przed odczytaniem lub zapisaniem jakichkolwiek danych. Jeśli reguły odmawiają dostępu do dowolnej ze ścieżek dokumentów, cała prośba kończy się niepowodzeniem.

Więcej informacji o programie Cloud Firestore Security Rules znajdziesz w artykule Pierwsze kroki z Cloud Firestore Security Rules.

Instalowanie emulatora

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

W wielu przypadkach należy uruchomić emulator, uruchomić zestaw testów, a potem zamknąć emulator po zakończeniu testów. Możesz to łatwo zrobić za pomocą polecenia emulators:exec:

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

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

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

Przed uruchomieniem emulatora

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

  • Na początku emulator wczyta reguły określone w polu firestore.rules pliku firebase.json. Wymaga ona podania nazwy pliku lokalnego zawierającego reguły Cloud Firestore Security Rules i zastosowuje te reguły do wszystkich projektów. Jeśli nie podasz ścieżki do pliku lokalnego ani nie użyjesz metody loadFirestoreRules opisanej poniżej, emulator będzie traktować wszystkie projekty jako mające otwarte reguły.
  • Chociaż większości pakietów SDK Firebase można używać bezpośrednio z emulatorami, tylko biblioteka @firebase/rules-unit-testing obsługuje symulowanie auth w regułach zabezpieczeń, co znacznie ułatwia testy jednostkowe. Dodatkowo biblioteka obsługuje kilka funkcji związanych z konkretnym emulatorem, takich jak czyszczenie wszystkich danych, które wymieniono poniżej.
  • Emulatorów można też używać do testowania integracji i ręcznych testów, łącząc aplikację bezpośrednio z emulatorami. W tym celu emulatory będą akceptować tokeny uwierzytelniania Firebase w wersji produkcyjnej udostępniane za pomocą pakietów SDK klienta i odpowiednio oceniać reguły.

Wykonywanie testów jednostkowych na komputerze lokalnym

Uruchom testy jednostkowe na komputerze za pomocą pakietu SDK JavaScript w wersji 9

Firebase udostępnia bibliotekę do testowania jednostek reguł bezpieczeństwa zarówno z pakietem SDK JavaScript w wersji 9, jak i z pakietem SDK w wersji 8. Interfejsy API bibliotek są znacznie inne. Zalecamy bibliotekę testów v9, która jest bardziej uproszczona i wymaga mniej konfiguracji do połączenia z emulatorami, dzięki czemu można bezpiecznie uniknąć przypadkowego użycia zasobów produkcyjnych. Ze względu na zgodność wsteczną nadal udostępniamy bibliotekę testów w wersji 8.

Użyj modułu @firebase/rules-unit-testing, aby wchodzić w interakcje z emulatorem działającym lokalnie. Jeśli wystąpią przekroczenia limitu czasu lub błędy ECONNREFUSED, sprawdź, czy emulator rzeczywiście działa.

Zdecydowanie zalecamy korzystanie z najnowszej wersji Node.js, aby można było używać notacji async/await. Prawie wszystkie zachowania, które chcesz przetestować, obejmują funkcje asynchroniczne, a moduł testowania jest zaprojektowany do współpracy z kodem opartym na obietnicach.

Biblioteka testowania jednostkowego reguł v9 jest zawsze świadoma emulatorów i nigdy nie dotyka Twoich zasobów produkcyjnych.

Bibliotekę importujesz za pomocą instrukcji importu modułowego v9. 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 testów jednostkowych należy:

  • Tworzenie i konfigurowanie RulesTestEnvironment za pomocą wywołania funkcji initializeTestEnvironment.
  • Konfigurowanie danych testowych bez uruchamiania reguł za pomocą wygodnej metody, która pozwala na ich tymczasowe pominięcie:RulesTestEnvironment.withSecurityRulesDisabled.
  • Konfigurowanie zestawu testów i funkcji przed testem lub po nim za pomocą wywołań służących do czyszczenia danych testowych i środowiska, takich jak RulesTestEnvironment.cleanup() lub RulesTestEnvironment.clearFirestore().
  • Wdrażanie przypadków testowych, które symulują stany uwierzytelniania za pomocą RulesTestEnvironment.authenticatedContext i RulesTestEnvironment.unauthenticatedContext.

Typowe metody i funkcje użyteczności

Zobacz też metody testów w emulatorze w pakiecie SDK w wersji 9.

initializeTestEnvironment() => RulesTestEnvironment

Ta funkcja inicjuje środowisko testowe do testowania reguł jednostkowych. Aby skonfigurować test, najpierw wywołaj tę funkcję. Aby uruchomić test, musisz uruchomić emulatory.

Funkcja akceptuje opcjonalny obiekt definiujący TestEnvironmentConfig, który może zawierać identyfikator projektu i ustawienia 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 zachowuje się jak uwierzytelniony użytkownik. Żądania utworzone za pomocą zwróconego kontekstu będą zawierać dołączony fałszywy token uwierzytelniający. Opcjonalnie prześlij obiekt definiujący roszczenia niestandardowe lub zastąpienia dla danych tokena uwierzytelniania.

Używaj zwracanego obiektu test context w testach, aby uzyskać dostęp do skonfigurowanych instancji emulatora, w tym do tych skonfigurowanych za pomocą 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

Ta metoda tworzy RulesTestContext, który zachowuje się jak klient, który nie zalogował się za pomocą uwierzytelniania. Żądania utworzone za pomocą zwróconego kontekstu nie będą zawierać tokenów Uwierzytelniania Firebase.

Używaj zwracanego obiektu test context w testach, aby uzyskać dostęp do skonfigurowanych instancji emulatora, w tym do tych skonfigurowanych za pomocą 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()

Uruchom funkcję testowego ustawienia z kontekstem, który zachowuje się tak, jakby reguły zabezpieczeń były wyłączone.

Ta metoda przyjmuje funkcję wywołania zwrotnego, która przyjmuje kontekst pomijania reguł bezpieczeństwa i zwraca obietnicę. Kontekst zostanie usunięty, gdy obietnica zostanie spełniona lub odrzucona.

RulesTestEnvironment.cleanup()

Ta metoda usuwa wszystkie RulesTestContexts utworzone w środowisku testowym i oczyszcza zasoby, co umożliwia prawidłowe zakończenie.

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

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

To jest funkcja pomocnicza testowego przypadku użycia.

Funkcja stwierdza, że dostarczona obietnica obejmująca operację emulatora zostanie rozwiązana bez naruszenia reguł zabezpieczeń.

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

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

To jest funkcja pomocnicza testowego przypadku użycia.

Funkcja stwierdza, że dostarczona obietnica obejmująca działanie emulatora zostanie odrzucona z powodu naruszenia reguł bezpieczeństwa.

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

Metody dotyczące emulatora

Zobacz też popularne metody testowania i funkcje pomocnicze w pakiecie SDK w wersji 9.

RulesTestEnvironment.clearFirestore() => Promise<void>

Ta metoda usuwa dane z bazy danych Firestore należącej do instancji projectId skonfigurowanej dla emulatora Firestore.

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

Ta metoda pobiera instancję Firestore dla tego testowanego kontekstu. Zwrócony obiekt pakietu SDK klienta Firebase JS można używać z interfejsami API pakietu SDK klienta (wersja 9 modularna lub kompatybilna z wersją 9).

Wizualizacja oceny reguł

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

Aby wyświetlić szczegółową sekwencję oceny dla każdej prośby, otwórz kartę Firestore > Prośby.

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

Generowanie raportów testowych

Po uruchomieniu zestawu testów możesz uzyskać dostęp do raportów o zakresie testów, które pokazują, jak oceniono każdą z reguł bezpieczeństwa.

Aby uzyskać raporty, prześlij zapytanie do punktu końcowego w emulatorze, gdy jest on uruchomiony. Aby uzyskać wersję przyjazną przeglądarce, użyj tego adresu URL:

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

Dzięki temu reguły są dzielone na wyrażenia i podwyrażenia, nad którymi możesz najechać kursorem, aby uzyskać więcej informacji, w tym liczbę zwracanych ocen i wartości. Aby uzyskać wersję tych danych w postaci surowego pliku JSON, dodaj do zapytania ten adres URL:

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

Różnice między emulatorem a wersją produkcyjną

  1. Nie musisz tworzyć projektu Cloud Firestore. Emulator automatycznie tworzy każdą instancję, do której uzyskuje dostęp.
  2. Emulator Cloud Firestore nie działa w ramach normalnego przepływu danych Firebase Authentication. Zamiast tego w pakiecie Firebase Test SDK udostępniliśmy w bibliotece rules-unit-testing metodę initializeTestApp(), która przyjmuje pole auth. Element Firebase utworzony za pomocą tej metody będzie się zachowywać tak, jakby został uwierzytelniony jako dowolny podany przez Ciebie podmiot. Jeśli przekażesz wartość null, będzie ona zachowywać się jak niezalogowany użytkownik (np. reguły auth != null nie będą działać).

Rozwiązywanie znanych problemów

Podczas korzystania z emulatora Cloud Firestore możesz napotkać te znane problemy. Aby rozwiązać problemy z nieprawidłowym działaniem, postępuj zgodnie z instrukcjami poniżej. Te uwagi dotyczą testowania jednostkowego biblioteki reguł zabezpieczeń, ale ogólne podejścia można stosować do dowolnego pakietu Firebase SDK.

Zachowanie testu jest niespójne

Jeśli testy są czasem zaliczane, a czasem nie, nawet bez wprowadzania zmian w samych testach, konieczne może być sprawdzenie, czy są one prawidłowo uporządkowane. Większość interakcji z emulatorem jest asynchroniczna, więc sprawdź, czy cały kod asynchroniczny jest poprawnie uporządkowany. Możesz poprawić kolejność, łącząc obie obietnice lub stosując notację await.

Zwróć szczególną uwagę na te operacje asynchroniczne:

  • Ustawianie reguł zabezpieczeń, na przykład initializeTestEnvironment.
  • odczytywanie i zapisywanie danych, na przykład db.collection("users").doc("alice").get().
  • oświadczenia operacyjne, w tym assertSucceedsassertFails;

Testy przechodzą tylko przy pierwszym uruchomieniu emulatora.

Emulator jest stanowy. Przechowuje wszystkie dane zapisane w pamięci, więc podczas zamykania emulatora nie ma utraty danych. Jeśli przeprowadzasz wiele testów dotyczących tego samego identyfikatora projektu, każdy z nich może wygenerować dane, które mogą wpływać na kolejne testy. Aby ominąć to zachowanie, możesz użyć dowolnej z tych metod:

  • Używaj unikalnych identyfikatorów projektów w przypadku każdego testu. Jeśli zdecydujesz się na to, musisz wywołać funkcję initializeTestEnvironment w ramach każdego testu. Reguły są automatycznie wczytywane tylko w przypadku domyślnego identyfikatora projektu.
  • Zmień strukturę testów, aby nie oddziaływały na wcześniej zapisane dane (np. używaj innej kolekcji dla każdego testu).
  • usunąć wszystkie dane zapisane podczas testu.

Konfiguracja testu jest bardzo skomplikowana

Podczas konfigurowania testu możesz chcieć zmodyfikować dane w sposób, który nie jest obsługiwany przez Cloud Firestore Security Rules. Jeśli Twoje reguły powodują, że konfiguracja testu jest skomplikowana, spróbuj użyć RulesTestEnvironment.withSecurityRulesDisabled w krokach konfiguracji, aby odczyty i zapisy nie powodowały błędów PERMISSION_DENIED.

Następnie test może wykonywać operacje jako uwierzytelniony lub niezautentifikowany użytkownik, odpowiednio za pomocą funkcji RulesTestEnvironment.authenticatedContext lub unauthenticatedContext. Dzięki temu możesz sprawdzić, czy Cloud Firestore Security Rules poprawnie zezwala na dostęp lub odmawia go w różnych przypadkach.