Testowanie reguł zabezpieczeń Cloud Firestore

Podczas tworzenia aplikacji możesz zablokować dostęp do swojej Cloud Firestore bazy danych. Zanim jednak opublikujesz aplikację, musisz mieć bardziej szczegółowe Cloud Firestore Security Rules. Za pomocą emulatora Cloud Firestore możesz nie tylko tworzyć prototypy i testować ogólne funkcje i zachowanie aplikacji, ale też pisać testy jednostkowe, które sprawdzają działanie Cloud Firestore Security Rules.

Krótkie wprowadzenie

Aby zapoznać się z kilkoma podstawowymi przypadkami testowymi z prostymi regułami, wypróbuj przykład z krótkiego wprowadzenia.

Omówienie Cloud Firestore Security Rules

Wdrażaj Firebase Authentication i Cloud Firestore Security Rules na potrzeby uwierzytelniania, autoryzacji i weryfikacji danych bezserwerowych, gdy używasz 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 Cloud Firestore jest oceniane na podstawie reguł zabezpieczeń przed odczytaniem lub zapisaniem 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 Wprowadzenie do Cloud Firestore Security Rules.

Instalowanie emulatora

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

firebase setup:emulators:firestore

Uruchamianie emulatora

Zacznij od zainicjowania projektu w 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 chcesz uruchomić emulator, przeprowadzić zestaw testów, a następnie zamknąć go 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 będzie próbował działać na porcie domyślnym (8080). Port emulatora możesz zmienić, modyfikując sekcję "emulators" w pliku firebase.json file:

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

Przed uruchomieniem emulatora

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

  • Emulator początkowo wczyta reguły określone w polu firestore.rules w pliku firebase.json. Oczekuje on 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 SDK Firebase działa bezpośrednio z emulatorami, tylko biblioteka @firebase/rules-unit-testing obsługuje tworzenie kopii zapasowych auth w regułach zabezpieczeń, co znacznie ułatwia testy jednostkowe. Dodatkowo biblioteka obsługuje kilka funkcji specyficznych dla emulatora, takich jak czyszczenie wszystkich danych, które są wymienione poniżej.
  • Emulatory będą też akceptować tokeny uwierzytelniania Firebase w wersji produkcyjnej dostarczane przez pakiety SDK klienta i odpowiednio oceniać reguły, co umożliwia bezpośrednie połączenie aplikacji z emulatorami w testach integracyjnych i ręcznych.

Uruchamianie lokalnych testów jednostkowych

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

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

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

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

Biblioteka testów jednostkowych reguł w wersji 9 zawsze wie o emulatorach i nigdy nie dotyka 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:

  • Utworzenie i skonfigurowanie RulesTestEnvironment za pomocą wywołania initializeTestEnvironment.
  • Konfigurowanie danych testowych bez wywoływania reguł za pomocą wygodnej metody, która umożliwia tymczasowe ich pominięcie, RulesTestEnvironment.withSecurityRulesDisabled.
  • Konfigurowanie zestawu testów i haków przed/po teście za pomocą wywołań zwalniania miejsca na dane testowe i środowisko, takich jak RulesTestEnvironment.cleanup() lub RulesTestEnvironment.clearFirestore().
  • Implementowanie przypadków testowych, które naśladują stany uwierzytelniania, za pomocą RulesTestEnvironment.authenticatedContext i RulesTestEnvironment.unauthenticatedContext.

Typowe metody i funkcje narzędziowe

Zobacz też metody testowe specyficzne dla emulatora w pakiecie SDK w wersji 9.

initializeTestEnvironment() => RulesTestEnvironment

Ta funkcja inicjuje środowisko testowe na potrzeby testów jednostkowych reguł. Wywołaj tę funkcję jako pierwszą w konfiguracji testu. Aby wykonanie się powiodło, emulatory muszą być uruchomione.

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 RulesTestContext, który zachowuje się jak uwierzytelniony użytkownik uwierzytelniania. Żądania utworzone za pomocą zwróconego kontekstu będą miały dołączony token uwierzytelniania. Opcjonalnie możesz przekazać obiekt definiujący niestandardowe deklaracje lub zastąpienia ładunków tokena 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 RulesTestContext, który zachowuje się jak klient niezalogowany za pomocą uwierzytelniania. Żądania utworzone za pomocą zwróconego kontekstu nie będą miały dołączonych tokenów uwierzytelniania Firebase.

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 pomijający reguły zabezpieczeń i zwraca obietnicę. Kontekst zostanie zniszczony po rozwiązaniu lub odrzuceniu obietnicy.

RulesTestEnvironment.cleanup()

Ta metoda niszczy wszystkie RulesTestContexts utworzone w środowisku testowym i czyści zasoby bazowe, co umożliwia czyste wyjście.

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

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

To funkcja użytkowa przypadku testowego.

Funkcja sprawdza, czy podana obietnica opakowująca operację emulatora zostanie rozwiązana bez naruszenia reguł zabezpieczeń.

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

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

To funkcja użytkowa przypadku testowego.

Funkcja sprawdza, czy podana obietnica opakowująca operację emulatora zostanie odrzucona z powodu naruszenia reguł zabezpieczeń.

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

Metody specyficzne dla emulatora

Zobacz też typowe metody testowe i funkcje narzędziowe 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óconą instancję pakietu Firebase JS Client SDK można używać z interfejsami API pakietu SDK klienta (modułowego w wersji 9 lub zgodnego w wersji 9).

Wizualizowanie ocen reguł

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

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

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

Generowanie raportów testowych

Po przeprowadzeniu zestawu testów możesz uzyskać dostęp do raportów pokrycia testami, które pokazują, jak oceniana była każda z reguł zabezpieczeń.

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

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

Dzieli to reguły na wyrażenia i podwyrażenia, nad którymi możesz najechać kursorem, aby uzyskać więcej informacji, w tym liczbę ocen i zwrócone wartości. Aby uzyskać wersję tych danych w formacie 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 wyraźnie tworzyć projektu Cloud Firestore. Emulator automatycznie tworzy każdą instancję, do której uzyskuje się dostęp.
  2. Emulator Cloud Firestore nie działa ze standardowym przepływem Firebase Authentication. Zamiast tego w pakiecie Firebase Test SDK udostępniliśmy metodę initializeTestApp() w bibliotece rules-unit-testing, która przyjmuje pole auth. Uchwyt Firebase utworzony za pomocą tej metody będzie się zachowywać tak, jakby został pomyślnie uwierzytelniony jako podmiot, który podasz. Jeśli przekażesz wartość null, będzie się zachowywać jak nieuwierzytelniony użytkownik (auth != null reguły zakończą się niepowodzeniem, na przykład).

Rozwiązywanie znanych problemów

Podczas korzystania z emulatora Cloud Firestore możesz napotkać te znane problemy. Aby rozwiązać wszelkie nieprawidłowe zachowania, postępuj zgodnie z poniższymi wskazówkami. Te uwagi zostały napisane z myślą o bibliotece testów jednostkowych reguł zabezpieczeń, ale ogólne podejścia mają zastosowanie do każdego pakietu SDK Firebase.

Niespójne zachowanie testów

Jeśli testy czasami przechodzą, a czasami nie, nawet bez wprowadzania zmian w samych testach, może być konieczne sprawdzenie, 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. Kolejność możesz poprawić, łącząc obietnice lub używając notacji await.

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

  • Ustawianie reguł zabezpieczeń, np. za pomocą initializeTestEnvironment.
  • Odczytywanie i zapisywanie danych, np. za pomocą db.collection("users").doc("alice").get().
  • Asercje operacyjne, w tym assertSucceeds i assertFails.

Testy przechodzą tylko przy pierwszym wczytaniu emulatora

Emulator jest stanowy. Przechowuje wszystkie zapisane w nim dane w pamięci, więc wszystkie dane są tracone po zamknięciu emulatora. Jeśli uruchamiasz wiele testów na tym samym identyfikatorze projektu, każdy test może generować dane, które mogą wpływać na kolejne testy. Aby pominąć to zachowanie, możesz użyć jednej z tych metod:

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

Konfiguracja testu jest bardzo skomplikowana

Podczas konfigurowania testu możesz chcieć zmodyfikować dane w sposób, który Twoje Cloud Firestore Security Rules nie są dozwolone. 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. Umożliwia to sprawdzenie, czy reguły zabezpieczeń Cloud Firestore prawidłowo zezwalają na różne przypadki lub odmawiają dostępu.Cloud Firestore Security Rules