測試 Cloud Firestore 安全性規則

建構應用程式時,您可能想鎖定 Cloud Firestore 資料庫的存取權。不過,在正式推出前,您需要更細緻的Cloud Firestore Security Rules。除了原型設計和測試應用程式的一般功能和行為,您還可以使用 Cloud Firestore 模擬器編寫單元測試,檢查 Cloud Firestore Security Rules 的行為。

快速入門導覽課程

如要使用簡單規則進行幾項基本測試,請試用快速入門範例

瞭解 Cloud Firestore Security Rules

使用行動和網路用戶端程式庫時,請導入 Firebase AuthenticationCloud Firestore Security Rules,進行無伺服器服務驗證、授權和資料驗證。

Cloud Firestore Security Rules 包含兩部分:

  1. 用於識別資料庫中文件的 match 陳述式。
  2. 用來控管這些文件存取權的 allow 運算式。

Firebase Authentication 會驗證使用者的憑證,並為以使用者和角色為基礎的存取系統提供基礎。

Cloud Firestore行動/網路用戶端程式庫的每項資料庫要求,都會先經過安全性規則評估,再讀取或寫入任何資料。如果規則拒絕存取任何指定的文件路徑,整個要求就會失敗。

如要進一步瞭解 Cloud Firestore Security Rules,請參閱「開始使用 Cloud Firestore Security Rules」。

安裝模擬器

如要安裝 Cloud Firestore 模擬器,請使用 Firebase CLI 執行下列指令:

firebase setup:emulators:firestore

執行模擬器

首先,請在工作目錄中初始化 Firebase 專案。這是使用 Firebase CLI 時常見的第一個步驟。

firebase init

使用下列指令啟動模擬器。模擬器會持續執行,直到您終止程序為止:

firebase emulators:start --only firestore

在許多情況下,您會想啟動模擬器、執行測試套件,然後在測試執行完畢後關閉模擬器。您可以使用 emulators:exec 指令輕鬆完成這項操作:

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

啟動模擬器時,系統會嘗試在預設通訊埠 (8080) 上執行。如要變更模擬器通訊埠,請修改 firebase.json 檔案的 "emulators" 區段:

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

執行模擬器前的準備

開始使用模擬器前,請注意下列事項:

  • 模擬器一開始會載入 firebase.json 檔案 firestore.rules 欄位中指定的規則。這個指令會預期您提供包含 Cloud Firestore Security Rules 的本機檔案名稱,並將這些規則套用至所有專案。如果您未提供本機檔案路徑,或使用下文所述的 loadFirestoreRules 方法,模擬器會將所有專案視為具有開放規則。
  • 雖然大多數 Firebase SDK 都能直接與模擬器搭配使用,但只有 @firebase/rules-unit-testing 程式庫支援模擬安全性規則中的 auth,因此可大幅簡化單元測試。此外,程式庫還支援幾項模擬器專屬功能,例如清除所有資料,如下所示。
  • 模擬器也會接受透過 Client SDK 提供的正式版 Firebase Auth 權杖,並據此評估規則,因此您可以在整合和手動測試中,將應用程式直接連線至模擬器。

執行本機單元測試

使用 JavaScript SDK 第 9 版執行本機單元測試

Firebase 會透過第 9 版 JavaScript SDK 和第 8 版 SDK,發布安全規則單元測試程式庫。程式庫 API 有顯著差異。建議使用 v9 測試程式庫,這個程式庫更精簡,且連線至模擬器時所需的設定較少,因此可安全地避免誤用正式版資源。為確保回溯相容性,我們仍會提供 v8 測試程式庫

使用 @firebase/rules-unit-testing 模組與在本機執行的模擬器互動。如果發生逾時或 ECONNREFUSED 錯誤,請再次確認模擬器是否正在執行。

強烈建議使用最新版本的 Node.js,這樣就能使用 async/await 標記。您可能想測試的行為幾乎都涉及非同步函式,而測試模組的設計宗旨是搭配以 Promise 為基礎的程式碼運作。

v9 規則單元測試程式庫一律會偵測模擬器,絕不會存取您的正式版資源。

您可以使用 v9 模組化匯入陳述式匯入程式庫。例如:

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.

匯入後,實作單元測試的步驟如下:

  • 呼叫 initializeTestEnvironment 即可建立及設定 RulesTestEnvironment
  • 設定測試資料,但不觸發規則,使用可暫時略過規則的便利方法 RulesTestEnvironment.withSecurityRulesDisabled
  • 設定測試套件和每次測試前後的掛鉤,並呼叫 RulesTestEnvironment.cleanup()RulesTestEnvironment.clearFirestore() 等函式,清除測試資料和環境。
  • 使用 RulesTestEnvironment.authenticatedContextRulesTestEnvironment.unauthenticatedContext 實作模擬驗證狀態的測試案例。

常見方法和公用程式函式

另請參閱 v9 SDK 中的模擬器專用測試方法

initializeTestEnvironment() => RulesTestEnvironment

這個函式會初始化規則單元測試的測試環境。請先呼叫這個函式,進行測試設定。如要順利執行,必須先啟動模擬器。

這個函式會接受定義 TestEnvironmentConfig 的選用物件,其中可包含專案 ID 和模擬器設定。

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

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

這個方法會建立 RulesTestContext,其行為與經過驗證的 Authentication 使用者類似。透過傳回的內容建立的要求會附加模擬驗證權杖。視需要傳遞定義自訂聲明或覆寫驗證權杖酬載的物件。

在測試中使用傳回的測試內容物件,存取設定的任何模擬器執行個體,包括使用 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

這個方法會建立 RulesTestContext,其行為類似於未透過驗證登入的用戶端。透過傳回的內容建立要求時,不會附加 Firebase 驗證權杖。

在測試中使用傳回的測試內容物件,存取設定的任何模擬器執行個體,包括使用 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()

使用行為如同安全性規則已停用的內容,執行測試設定函式。

這個方法會採用回呼函式,該函式會採用 Security-Rules-bypassing 環境,並傳回 Promise。承諾解決 / 拒絕後,內容就會遭到破壞。

RulesTestEnvironment.cleanup()

這個方法會銷毀測試環境中建立的所有 RulesTestContexts,並清除基礎資源,以便順利結束。

這個方法不會以任何方式變更模擬器的狀態。如要在測試之間重設資料,請使用應用程式模擬器專屬的清除資料方法。

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

這是測試案例公用程式函式。

函式會斷言提供的 Promise 包裝模擬器作業將會解析,且不會違反安全規則。

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

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

這是測試案例公用程式函式。

這項函式會斷言,系統將拒絕提供的 Promise (包裝模擬器作業),並違反安全規則。

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

模擬器專屬方法

另請參閱 v9 SDK 中的常見測試方法和公用程式函式

RulesTestEnvironment.clearFirestore() => Promise<void>

這個方法會清除 Firestore 資料庫中屬於 projectId 的資料,這些資料已設定為 Firestore 模擬器。

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

這個方法會取得此測試環境的 Firestore 執行個體。傳回的 Firebase JS Client SDK 執行個體可用於 Client SDK API (v9 模組化或 v9 相容)。

以視覺化方式呈現規則評估結果

您可以使用 Cloud Firestore 模擬器,在模擬器套件 UI 中查看用戶端要求,包括 Firebase 安全性規則的評估追蹤記錄。

開啟「Firestore」>「要求」分頁,查看每項要求的詳細評估順序。

Firestore 模擬器要求監控器,顯示安全性規則評估結果

產生測試報告

執行一系列測試後,您就能存取測試涵蓋範圍報表,瞭解每項安全性規則的評估方式。

如要取得報表,請在模擬器執行時查詢公開端點。如要使用瀏覽器友善版本,請前往下列網址:

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

這會將規則拆解為運算式和子運算式,您可以將滑鼠游標懸停在這些項目上,查看更多資訊,包括評估次數和傳回的值。如要取得這項資料的原始 JSON 版本,請在查詢中加入下列網址:

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

模擬器與正式環境的差異

  1. 您不必明確建立 Cloud Firestore 專案。模擬器會自動建立所存取的任何執行個體。
  2. Cloud Firestore 模擬器不適用於一般 Firebase Authentication 流程。 我們在 Firebase 測試 SDK 的 rules-unit-testing 程式庫中提供 initializeTestApp() 方法,可接受 auth 欄位。使用這個方法建立的 Firebase 控制代碼,會如同您提供的任何實體已成功通過驗證。如果您傳遞 null,系統會將其視為未經驗證的使用者 (例如,auth != null 規則會失敗)。

排解已知問題

使用 Cloud Firestore 模擬器時,可能會遇到下列已知問題。請按照下方指引排解任何異常行為。這些注意事項是針對安全防護規則單元測試程式庫編寫,但一般方法適用於任何 Firebase SDK。

測試行為不一致

如果測試偶爾會通過,偶爾會失敗,即使測試本身沒有任何變更,您可能也需要確認測試是否已正確排序。與模擬器的互動大多為非同步,因此請仔細檢查所有非同步程式碼是否依序執行。如要修正順序,可以串連 Promise,或大量使用 await 標記。

請特別查看下列非同步作業:

  • 設定安全性規則,例如 initializeTestEnvironment
  • 讀取及寫入資料,例如 db.collection("users").doc("alice").get()
  • 作業判斷,包括 assertSucceedsassertFails

測試只會在首次載入模擬器時通過

模擬器是有狀態的。模擬器會將所有寫入的資料儲存在記憶體中,因此只要模擬器關閉,所有資料就會遺失。如果您針對同一個專案 ID 執行多項測試,每項測試都可能產生影響後續測試的資料。您可以使用下列任一方法略過這項行為:

  • 請為每項測試使用專屬專案 ID。請注意,如果您選擇這麼做,就必須在每個測試中呼叫 initializeTestEnvironment;系統只會自動載入預設專案 ID 的規則。
  • 重新架構測試,確保測試不會與先前寫入的資料互動 (例如,為每個測試使用不同的集合)。
  • 刪除測試期間寫入的所有資料。

測試設定非常複雜

設定測試時,您可能會想以 Cloud Firestore Security Rules 實際不允許的方式修改資料。如果規則導致測試設定變得複雜,請嘗試在設定步驟中使用 RulesTestEnvironment.withSecurityRulesDisabled,這樣讀取和寫入作業就不會觸發 PERMISSION_DENIED 錯誤。

之後,您的測試就能分別使用 RulesTestEnvironment.authenticatedContextunauthenticatedContext,以已驗證或未驗證使用者的身分執行作業。這可讓您驗證 Cloud Firestore Security Rules 是否正確允許 / 拒絕不同情況。