測試 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,因此單元測試會變得更加簡單。此外,此程式庫支援一些模擬器專屬功能,例如清除所有資料,如下所列。
  • 模擬器也會接受透過用戶端 SDK 提供的正式版 Firebase 驗證權杖,並據此評估規則,讓您在整合和手動測試時,直接將應用程式連結至模擬器。

執行本機單元測試

使用 v9 JavaScript SDK 執行本機單元測試

Firebase 會透過其第 9 版 JavaScript SDK 和第 8 版 SDK 發布安全規則單元測試程式庫。程式庫 API 有顯著差異。我們建議您使用 v9 測試程式庫,因為它更精簡,且不需要太多設定就能連線至模擬器,因此可安全避免意外使用正式版資源。為了回溯相容性,我們會繼續提供 v8 測試程式庫

使用 @firebase/rules-unit-testing 模組與本機執行的模擬器互動。如果出現逾時或 ECONNREFUSED 錯誤,請仔細檢查模擬器是否確實正在執行。

強烈建議您使用最新版的 Node.js,以便使用 async/await 符號。您可能想要測試的幾乎所有行為都涉及非同步函式,而測試模組則是為了與以承諾為基礎的程式碼搭配使用而設計。

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 實作模擬驗證狀態的測試案例。

常用方法和公用程式函式

另請參閱 第 9 版 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'), { ... });

模擬器專用方法

另請參閱 第 9 版 SDK 中的常見測試方法和公用函式

RulesTestEnvironment.clearFirestore() => Promise<void>

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

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

這個方法會為此測試內容取得 Firestore 例項。傳回的 Firebase JS Client SDK 例項可搭配用戶端 SDK API (第 9 版模組化或第 9 版相容性) 使用。

以圖表呈現規則評估結果

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 Test SDK 中提供 rules-unit-testing 程式庫中的 initializeTestApp() 方法,該方法會使用 auth 欄位。使用這個方法建立的 Firebase 句柄,會以已成功驗證為您提供的實體的形式運作。如果您傳入 null,系統會將其視為未經驗證的使用者 (例如 auth != null 規則會失敗)。

排解已知問題

使用 Cloud Firestore 模擬器時,您可能會遇到下列已知問題。請按照下列指示排解您遇到的任何異常行為。這些筆記是以安全規則單元測試程式庫為依據,但一般方法適用於任何 Firebase SDK。

測試行為不一致

如果測試偶爾會通過或失敗,即使測試本身沒有任何變更,您可能需要驗證測試是否有正確的順序。與模擬器的大部分互動都是非同步的,因此請仔細檢查所有非同步程式碼是否已正確排序。您可以透過連結承諾或大量使用 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 是否正確允許 / 拒絕不同情況。