Testowanie reguł zabezpieczeń Cloud Firestore

Podczas tworzenia aplikacji możesz zablokować dostęp do bazy danych Cloud Firestore. Jednak przed uruchomieniem kampanii potrzebujesz bardziej szczegółowych informacji.Cloud Firestore Security Rules Emulator Cloud Firestore umożliwia nie tylko prototypowanie i testowanie ogólnych funkcji i zachowania aplikacji, ale też pisanie testów jednostkowych, które sprawdzają zachowanie Cloud Firestore Security Rules.

Krótkie wprowadzenie

Aby zapoznać się z kilkoma podstawowymi elementami testowania z prostymi regułami, wypróbuj przykładowe krótkie wprowadzenie.

Zrozumienie: Cloud Firestore Security Rules

Zaimplementuj Firebase Authentication i Cloud Firestore Security Rules na potrzeby bezserwerowego uwierzytelniania, autoryzacji i weryfikacji danych, gdy korzystasz z bibliotek klienta mobilnego i internetowego.

Cloud Firestore Security Rules obejmują 2 elementy:

  1. Instrukcja match, która identyfikuje dokumenty w bazie danych.
  2. Wyrażenie allow, które kontroluje 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 lub internetowego jest sprawdzane pod kątem reguł zabezpieczeń przed odczytem lub zapisem danych. Jeśli reguły odmawiają dostępu do którejkolwiek z określonych ścieżek dokumentów, całe żądanie kończy się niepowodzeniem.

Więcej informacji o 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 w Firebase w katalogu roboczym. Jest to częsty 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

W wielu przypadkach chcesz uruchomić emulator, przeprowadzić zestaw testów, a następnie wyłączyć emulator. Możesz to łatwo zrobić za pomocą polecenia emulators:exec:

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

Po uruchomieniu emulator spróbuje działać na domyślnym porcie (8080). Możesz zmienić port emulatora, modyfikując sekcję "emulators" w 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 nazwy pliku lokalnego zawierającego Cloud Firestore Security Rules i stosuje te reguły do wszystkich projektów. Jeśli nie podasz ścieżki do pliku lokalnego lub nie użyjesz metody loadFirestoreRules opisanej poniżej, emulator będzie traktować wszystkie projekty jako mające otwarte reguły.
  • Chociaż większość pakietów Firebase SDK działa bezpośrednio z emulatorami, tylko biblioteka @firebase/rules-unit-testing obsługuje makiety auth w regułach zabezpieczeń, co znacznie ułatwia testy jednostkowe. Dodatkowo biblioteka obsługuje kilka funkcji emulatora, takich jak czyszczenie wszystkich danych, które wymieniono poniżej.
  • Emulatory będą też akceptować produkcyjne tokeny Firebase Auth dostarczane przez pakiety SDK klienta i odpowiednio oceniać reguły, co umożliwia łączenie aplikacji bezpośrednio z emulatorami w testach integracyjnych i manualnych.

Uruchamianie lokalnych testów jednostkowych

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

Firebase udostępnia bibliotekę do testów jednostkowych reguł zabezpieczeń zarówno w pakiecie JavaScript SDK w wersji 9, jak i w pakiecie SDK w wersji 8. Interfejsy API bibliotek znacznie się różnią. Zalecamy używanie biblioteki testowej w wersji 9, która jest bardziej uproszczona i wymaga mniej konfiguracji do łączenia się z emulatorami, co pozwala bezpiecznie uniknąć przypadkowego użycia zasobów produkcyjnych. Aby zapewnić zgodność wsteczną, nadal udostępniamy bibliotekę testową w wersji 8.

Użyj modułu @firebase/rules-unit-testing do interakcji z emulatorem, który działa lokalnie. Jeśli wystąpią błędy przekroczenia limitu czasu lub błędy ECONNREFUSED, sprawdź, czy emulator jest uruchomiony.

Zdecydowanie zalecamy korzystanie z najnowszej wersji Node.js, aby móc używać zapisu async/await. Prawie wszystkie zachowania, które możesz chcieć przetestować, obejmują funkcje asynchroniczne, a moduł testowy jest zaprojektowany do pracy z kodem opartym na obietnicach.

Biblioteka testów jednostkowych reguł w wersji 9 zawsze wie o emulatorach i nigdy nie korzysta z zasobów produkcyjnych.

Bibliotekę importujesz za pomocą instrukcji importu modułowego 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 implementacja testów jednostkowych obejmuje:

  • Tworzenie i konfigurowanie obiektu RulesTestEnvironment za pomocą wywołania metody initializeTestEnvironment.
  • skonfigurować dane testowe bez aktywowania reguł, korzystając z wygodnej metody, która pozwala tymczasowo je pominąć (RulesTestEnvironment.withSecurityRulesDisabled);
  • Konfigurowanie zestawu testów i punktów zaczepienia przed i po każdym teście z wywołaniami do czyszczenia danych i środowiska testowego, takich jak RulesTestEnvironment.cleanup() lub RulesTestEnvironment.clearFirestore().
  • wdrażanie elementów testowania, które naśladują stany uwierzytelniania za pomocą RulesTestEnvironment.authenticatedContext i RulesTestEnvironment.unauthenticatedContext.

Typowe metody i funkcje użytkowe

Zobacz też metody testowania emulatora w pakiecie SDK w wersji 9.

initializeTestEnvironment() => RulesTestEnvironment

Ta funkcja inicjuje środowisko testowe na potrzeby testowania jednostkowego reguł. Najpierw wywołaj tę funkcję, aby skonfigurować test. Do prawidłowego działania wymagane jest uruchomienie emulatorów.

Funkcja 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 zachowuje się jak uwierzytelniony użytkownik Authentication. Żądania utworzone za pomocą zwróconego kontekstu będą miały dołączony próbny token uwierzytelniania. Opcjonalnie możesz przekazać obiekt definiujący niestandardowe oświadczenia lub zastąpienia ładunków tokenów uwierzytelniania.

Użyj zwróconego obiektu kontekstu testowego w testach, aby uzyskać dostęp do wszystkich skonfigurowanych instancji emulatora, w tym 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().doc('/users/alice'), { ... });

RulesTestEnvironment.unauthenticatedContext() => RulesTestContext

Ta metoda tworzy obiekt RulesTestContext, który zachowuje się jak klient niezalogowany przez uwierzytelnianie. Żądania utworzone za pomocą zwróconego kontekstu nie będą miały dołączonych tokenów Firebase Auth.

Użyj zwróconego obiektu kontekstu testowego w testach, aby uzyskać dostęp do wszystkich skonfigurowanych instancji emulatora, w tym 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ę konfiguracji testu 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ł zabezpieczeń i zwraca obietnicę. Kontekst zostanie zniszczony po rozwiązaniu lub odrzuceniu obietnicy.

RulesTestEnvironment.cleanup()

Ta metoda niszczy wszystkie instancje RulesTestContexts utworzone w środowisku testowym i czyści wykorzystywane zasoby, umożliwiając czyste wyjście.

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

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

To jest funkcja użytkowa elementu testowania.

Funkcja potwierdza, że dostarczony obiekt Promise opakowujący operację emulatora zostanie rozwiązany bez naruszenia reguł zabezpieczeń.

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

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

To jest funkcja użytkowa elementu testowania.

Funkcja potwierdza, że dostarczona obietnica opakowująca operację emulatora zostanie odrzucona z powodu naruszenia reguł zabezpieczeń.

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

Metody dotyczące emulatora

Zobacz też typowe metody testowania i funkcje użytkowe w pakiecie SDK w wersji 9.

RulesTestEnvironment.clearFirestore() => Promise<void>

Ta metoda czyści dane w bazie danych Firestore, które należą do projectId skonfigurowanego dla emulatora Firestore.

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

Ta metoda pobiera instancję Firestore dla tego kontekstu testowego. Zwróconej instancji pakietu Firebase JS Client SDK 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 oceny reguł zabezpieczeń Firebase.

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

Monitor żądań emulatora Firestore z ocenami reguł zabezpieczeń

Generowanie raportów z testów

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

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

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

Spowoduje to podzielenie reguł na wyrażenia i podwyrażenia, na które możesz najechać kursorem, aby uzyskać więcej informacji, w tym liczbę ocen i zwrócone wartości. Aby uzyskać nieprzetworzoną wersję JSON tych danych, dodaj do zapytania ten adres URL:

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

Różnice między emulatorem a środowiskiem produkcyjnym

  1. Nie musisz tworzyć projektu Cloud Firestore. Emulator automatycznie tworzy każdą instancję, do której uzyskano dostęp.
  2. Emulator Cloud Firestore nie działa z normalnym procesem Firebase Authentication. Zamiast tego w pakiecie SDK Firebase Test udostępniliśmy metodę initializeTestApp() w bibliotece rules-unit-testing, która przyjmuje pole auth. Uchwyt Firebase utworzony za pomocą tej metody będzie zachowywać się tak, jakby został pomyślnie uwierzytelniony jako podany przez Ciebie podmiot. Jeśli przekażesz wartość null, będzie ona traktowana jako nieuwierzytelniony 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 poniższymi wskazówkami. Te uwagi zostały przygotowane z myślą o bibliotece testów jednostkowych reguł zabezpieczeń, ale ogólne podejście można zastosować do dowolnego pakietu Firebase SDK.

Zachowanie testowe jest niespójne

Jeśli testy czasami kończą się powodzeniem, a czasami nie, nawet bez wprowadzania w nich zmian, być może musisz sprawdzić, czy są one prawidłowo uporządkowane. Większość interakcji z emulatorem jest asynchroniczna, dlatego sprawdź, czy cały kod asynchroniczny jest prawidłowo uporządkowany. Możesz naprawić sekwencjonowanie, łącząc obietnice lub swobodnie używając zapisu await.

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

  • ustawianie reguł zabezpieczeń, na przykład za pomocą initializeTestEnvironment;
  • odczytywanie i zapisywanie danych, np. za pomocą db.collection("users").doc("alice").get();
  • twierdzenia operacyjne, w tym assertSucceeds i assertFails;

Testy przechodzą tylko przy pierwszym wczytaniu emulatora

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

  • Używaj unikalnych identyfikatorów projektów w każdym teście. Pamiętaj, że jeśli zdecydujesz się na to, w ramach każdego testu musisz wywołać funkcję initializeTestEnvironment. Reguły są automatycznie wczytywane tylko w przypadku domyślnego identyfikatora projektu.
  • Zmień strukturę testów, aby nie wchodziły w interakcje z wcześniej zapisanymi danymi (np. używaj innej kolekcji w każdym teście).
  • Usuń wszystkie dane zapisane podczas testu.

Konfiguracja testu jest bardzo skomplikowana

Podczas konfigurowania testu możesz zmodyfikować dane w sposób, na który Cloud Firestore Security Rules nie zezwala. Jeśli reguły utrudniają konfigurację testu, 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 nieuwierzytelniony użytkownik, używając odpowiednio RulesTestEnvironment.authenticatedContext i unauthenticatedContext. Dzięki temu możesz sprawdzić, czy Cloud Firestore Security Rules prawidłowo zezwala na różne przypadki lub je odrzuca.