Kiểm thử Quy tắc bảo mật của Cloud Firestore

Khi tạo ứng dụng, bạn có thể muốn hạn chế quyền truy cập vào cơ sở dữ liệu Cloud Firestore. Tuy nhiên, trước khi ra mắt, bạn sẽ cần có Cloud Firestore Security Rules chi tiết hơn. Với trình mô phỏng Cloud Firestore, ngoài việc tạo mẫu và kiểm thử các tính năng và hành vi chung của ứng dụng, bạn có thể viết các bài kiểm thử đơn vị để kiểm tra hành vi của Cloud Firestore Security Rules.

Bắt đầu nhanh

Đối với một số trường hợp kiểm thử cơ bản có quy tắc đơn giản, hãy dùng mẫu bắt đầu nhanh.

Tìm hiểu Cloud Firestore Security Rules

Triển khai Firebase AuthenticationCloud Firestore Security Rules để xác thực, uỷ quyền và xác thực dữ liệu không cần máy chủ khi bạn dùng thư viện ứng dụng di động và ứng dụng web.

Cloud Firestore Security Rules bao gồm 2 phần:

  1. Một câu lệnh match xác định các tài liệu trong cơ sở dữ liệu của bạn.
  2. Một biểu thức allow kiểm soát quyền truy cập vào các tài liệu đó.

Firebase Authentication xác minh thông tin đăng nhập của người dùng và cung cấp nền tảng cho các hệ thống truy cập dựa trên người dùng và vai trò.

Mọi yêu cầu về cơ sở dữ liệu từ thư viện ứng dụng di động/web Cloud Firestore đều được đánh giá dựa trên các quy tắc bảo mật của bạn trước khi đọc hoặc ghi bất kỳ dữ liệu nào. Nếu các quy tắc từ chối quyền truy cập vào bất kỳ đường dẫn tài liệu nào được chỉ định, thì toàn bộ yêu cầu sẽ không thành công.

Tìm hiểu thêm về Cloud Firestore Security Rules trong bài viết Bắt đầu sử dụng Cloud Firestore Security Rules.

Cài đặt trình mô phỏng

Để cài đặt trình mô phỏng Cloud Firestore, hãy dùng Firebase CLI và chạy lệnh bên dưới:

firebase setup:emulators:firestore

Chạy trình mô phỏng

Bắt đầu bằng cách khởi chạy một dự án Firebase trong thư mục làm việc của bạn. Đây là bước đầu tiên thường gặp khi sử dụng Giao diện dòng lệnh (CLI) của Firebase.

firebase init

Khởi động trình mô phỏng bằng lệnh sau. Trình mô phỏng sẽ chạy cho đến khi bạn dừng quy trình:

firebase emulators:start --only firestore

Trong nhiều trường hợp, bạn muốn khởi động trình mô phỏng, chạy một bộ kiểm thử, sau đó tắt trình mô phỏng sau khi các kiểm thử chạy. Bạn có thể dễ dàng thực hiện việc này bằng lệnh emulators:exec:

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

Khi khởi động, trình mô phỏng sẽ cố gắng chạy trên một cổng mặc định (8080). Bạn có thể thay đổi cổng trình mô phỏng bằng cách sửa đổi phần "emulators" của tệp firebase.json:

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

Trước khi bạn chạy trình mô phỏng

Trước khi bắt đầu sử dụng trình mô phỏng, hãy lưu ý những điều sau:

  • Ban đầu, trình mô phỏng sẽ tải các quy tắc được chỉ định trong trường firestore.rules của tệp firebase.json. Công cụ này dự kiến tên của một tệp cục bộ chứa Cloud Firestore Security Rules và áp dụng các quy tắc đó cho tất cả các dự án. Nếu bạn không cung cấp đường dẫn tệp cục bộ hoặc sử dụng phương thức loadFirestoreRules như mô tả bên dưới, thì trình mô phỏng sẽ coi tất cả các dự án đều có quy tắc mở.
  • Mặc dù hầu hết các Firebase SDK đều hoạt động trực tiếp với trình mô phỏng, nhưng chỉ có thư viện @firebase/rules-unit-testing hỗ trợ mô phỏng auth trong Quy tắc bảo mật, giúp các kiểm thử đơn vị trở nên dễ dàng hơn nhiều. Ngoài ra, thư viện này còn hỗ trợ một số tính năng dành riêng cho trình mô phỏng, chẳng hạn như xoá tất cả dữ liệu, như được liệt kê dưới đây.
  • Các trình mô phỏng cũng sẽ chấp nhận mã thông báo Firebase Auth do sản xuất được cung cấp thông qua SDK ứng dụng và đánh giá các quy tắc cho phù hợp. Điều này cho phép kết nối trực tiếp ứng dụng của bạn với các trình mô phỏng trong quá trình tích hợp và kiểm thử thủ công.

Chạy kiểm thử đơn vị cục bộ

Chạy kiểm thử đơn vị cục bộ bằng JavaScript SDK phiên bản 9

Firebase phân phối một thư viện kiểm thử đơn vị Security Rules (Quy tắc bảo mật) bằng cả SDK JavaScript phiên bản 9 và SDK phiên bản 8. Các API thư viện có sự khác biệt đáng kể. Bạn nên dùng thư viện kiểm thử phiên bản 9. Thư viện này tinh giản hơn và ít cần thiết lập để kết nối với trình mô phỏng, nhờ đó giúp bạn tránh vô tình sử dụng các tài nguyên sản xuất. Để đảm bảo khả năng tương thích ngược, chúng tôi tiếp tục cung cấp thư viện kiểm thử phiên bản 8.

Sử dụng mô-đun @firebase/rules-unit-testing để tương tác với trình mô phỏng chạy cục bộ. Nếu bạn gặp lỗi hết thời gian chờ hoặc lỗi ECONNREFUSED, hãy kiểm tra kỹ để đảm bảo trình mô phỏng đang chạy.

Bạn nên sử dụng phiên bản Node.js gần đây để có thể sử dụng ký hiệu async/await. Hầu hết hành vi mà bạn có thể muốn kiểm thử đều liên quan đến các hàm không đồng bộ và mô-đun kiểm thử được thiết kế để hoạt động với mã dựa trên Promise.

Thư viện Kiểm thử đơn vị theo quy tắc phiên bản 9 luôn nhận biết được các trình mô phỏng và không bao giờ ảnh hưởng đến tài nguyên sản xuất của bạn.

Bạn nhập thư viện bằng cách sử dụng câu lệnh nhập theo mô-đun phiên bản 9. Ví dụ:

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.

Sau khi nhập, việc triển khai kiểm thử đơn vị sẽ bao gồm:

  • Tạo và định cấu hình RulesTestEnvironment bằng lệnh gọi đến initializeTestEnvironment.
  • Thiết lập dữ liệu kiểm thử mà không kích hoạt Quy tắc, bằng cách sử dụng một phương thức thuận tiện cho phép bạn tạm thời bỏ qua các quy tắc đó, RulesTestEnvironment.withSecurityRulesDisabled.
  • Thiết lập bộ thử nghiệm và các lệnh gọi trước/sau mỗi thử nghiệm để dọn dẹp dữ liệu và môi trường thử nghiệm, chẳng hạn như RulesTestEnvironment.cleanup() hoặc RulesTestEnvironment.clearFirestore().
  • Triển khai các trường hợp kiểm thử mô phỏng trạng thái xác thực bằng cách sử dụng RulesTestEnvironment.authenticatedContextRulesTestEnvironment.unauthenticatedContext.

Các phương thức và hàm tiện ích phổ biến

Ngoài ra, hãy xem các phương thức kiểm thử dành riêng cho trình mô phỏng trong SDK phiên bản 9.

initializeTestEnvironment() => RulesTestEnvironment

Hàm này khởi tạo một môi trường kiểm thử để kiểm thử đơn vị quy tắc. Trước tiên, hãy gọi hàm này để thiết lập kiểm thử. Để thực thi thành công, bạn cần chạy trình mô phỏng.

Hàm này chấp nhận một đối tượng không bắt buộc xác định TestEnvironmentConfig, có thể bao gồm mã dự án và chế độ cài đặt cấu hình trình mô phỏng.

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

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

Phương thức này tạo ra một RulesTestContext, hoạt động giống như một người dùng Xác thực đã xác thực. Các yêu cầu được tạo thông qua ngữ cảnh được trả về sẽ có mã thông báo Xác thực mô phỏng được đính kèm. Bạn có thể tuỳ ý truyền một đối tượng xác định các thông tin xác nhận quyền sở hữu tuỳ chỉnh hoặc ghi đè cho tải trọng Mã thông báo xác thực.

Sử dụng đối tượng ngữ cảnh kiểm thử được trả về trong các kiểm thử để truy cập vào mọi thực thể trình mô phỏng đã định cấu hình, kể cả những thực thể được định cấu hình bằng 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

Phương thức này tạo ra một RulesTestContext, hoạt động như một ứng dụng khách không đăng nhập thông qua quy trình Xác thực. Các yêu cầu được tạo thông qua ngữ cảnh được trả về sẽ không có mã thông báo Xác thực Firebase được đính kèm.

Sử dụng đối tượng ngữ cảnh kiểm thử được trả về trong các kiểm thử để truy cập vào mọi thực thể trình mô phỏng đã định cấu hình, kể cả những thực thể được định cấu hình bằng 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()

Chạy một hàm thiết lập kiểm thử có ngữ cảnh hoạt động như thể Quy tắc bảo mật đã bị vô hiệu hoá.

Phương thức này sử dụng một hàm gọi lại, hàm này sẽ lấy ngữ cảnh Security-Rules-bypassing và trả về một promise. Ngữ cảnh sẽ bị huỷ sau khi lời hứa phân giải / từ chối.

RulesTestEnvironment.cleanup()

Phương thức này sẽ huỷ tất cả RulesTestContexts được tạo trong môi trường kiểm thử và dọn dẹp các tài nguyên cơ bản, cho phép thoát một cách sạch sẽ.

Phương thức này hoàn toàn không thay đổi trạng thái của trình mô phỏng. Để đặt lại dữ liệu giữa các lần kiểm thử, hãy sử dụng phương thức xoá dữ liệu dành riêng cho trình mô phỏng ứng dụng.

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

Đây là một hàm tiện ích của trường hợp kiểm thử.

Hàm này khẳng định rằng Promise được cung cấp bao bọc một thao tác trình mô phỏng sẽ được phân giải mà không có lỗi vi phạm Quy tắc bảo mật.

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

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

Đây là một hàm tiện ích của trường hợp kiểm thử.

Hàm này khẳng định rằng Promise được cung cấp bao bọc một thao tác trình mô phỏng sẽ bị từ chối do vi phạm Quy tắc bảo mật.

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

Các phương thức dành riêng cho trình mô phỏng

Bạn cũng có thể xem các phương thức kiểm thử và hàm tiện ích phổ biến trong SDK phiên bản 9.

RulesTestEnvironment.clearFirestore() => Promise<void>

Phương thức này xoá dữ liệu trong cơ sở dữ liệu Firestore thuộc projectId được định cấu hình cho trình mô phỏng Firestore.

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

Phương thức này lấy một thực thể Firestore cho ngữ cảnh kiểm thử này. Bạn có thể sử dụng phiên bản Firebase JS Client SDK được trả về với các API SDK ứng dụng (v9 theo mô-đun hoặc v9 tương thích).

Trực quan hoá việc đánh giá quy tắc

Trình mô phỏng Cloud Firestore cho phép bạn hình dung các yêu cầu của ứng dụng trong giao diện người dùng của Bộ công cụ mô phỏng, bao gồm cả tính năng theo dõi việc đánh giá cho Quy tắc bảo mật của Firebase.

Mở thẻ Firestore > Yêu cầu để xem trình tự đánh giá chi tiết cho từng yêu cầu.

Trình theo dõi yêu cầu của Trình mô phỏng Firestore cho thấy các lượt đánh giá Quy tắc bảo mật

Tạo báo cáo kiểm thử

Sau khi chạy một bộ kiểm thử, bạn có thể truy cập vào các báo cáo về mức độ kiểm thử cho biết cách đánh giá từng quy tắc bảo mật của bạn.

Để nhận báo cáo, hãy truy vấn một điểm cuối được hiển thị trên trình mô phỏng trong khi trình mô phỏng đang chạy. Để có phiên bản thân thiện với trình duyệt, hãy sử dụng URL sau:

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

Thao tác này sẽ chia các quy tắc của bạn thành các biểu thức và biểu thức phụ mà bạn có thể di chuột để biết thêm thông tin, bao gồm cả số lượng lượt đánh giá và giá trị được trả về. Để có phiên bản JSON thô của dữ liệu này, hãy thêm URL sau vào cụm từ tìm kiếm của bạn:

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

Sự khác biệt giữa trình mô phỏng và bản phát hành công khai

  1. Bạn không cần phải tạo một dự án Cloud Firestore một cách rõ ràng. Trình mô phỏng sẽ tự động tạo mọi phiên bản được truy cập.
  2. Trình mô phỏng Cloud Firestore không hoạt động với quy trình Firebase Authentication thông thường. Thay vào đó, trong Firebase Test SDK, chúng tôi đã cung cấp phương thức initializeTestApp() trong thư viện rules-unit-testing. Phương thức này lấy một trường auth. Firebase handle được tạo bằng phương thức này sẽ hoạt động như thể đã xác thực thành công dưới dạng bất kỳ thực thể nào mà bạn cung cấp. Nếu bạn truyền null, thì nó sẽ hoạt động như một người dùng chưa được xác thực (ví dụ: các quy tắc auth != null sẽ không thành công).

Khắc phục các vấn đề đã biết

Khi sử dụng trình mô phỏng Cloud Firestore, bạn có thể gặp phải các vấn đề đã biết sau đây. Hãy làm theo hướng dẫn bên dưới để khắc phục mọi hành vi bất thường mà bạn gặp phải. Những lưu ý này được viết dựa trên thư viện kiểm thử đơn vị Quy tắc bảo mật, nhưng các phương pháp chung có thể áp dụng cho mọi SDK Firebase.

Hành vi kiểm thử không nhất quán

Nếu các bài kiểm thử của bạn đôi khi đạt và đôi khi không đạt, ngay cả khi không có bất kỳ thay đổi nào đối với chính các bài kiểm thử, thì bạn có thể cần xác minh rằng các bài kiểm thử được sắp xếp đúng cách. Hầu hết các hoạt động tương tác với trình mô phỏng đều là không đồng bộ, vì vậy, hãy kiểm tra kỹ để đảm bảo tất cả mã không đồng bộ đều được sắp xếp đúng cách. Bạn có thể khắc phục trình tự bằng cách xâu chuỗi các promise hoặc sử dụng ký hiệu await một cách thoải mái.

Cụ thể, hãy xem xét các thao tác không đồng bộ sau:

  • Thiết lập các quy tắc bảo mật, chẳng hạn như initializeTestEnvironment.
  • Đọc và ghi dữ liệu, chẳng hạn như db.collection("users").doc("alice").get().
  • Các khẳng định về hoạt động, bao gồm assertSucceedsassertFails.

Các kiểm thử chỉ vượt qua lần đầu tiên bạn tải trình mô phỏng

Trình mô phỏng có trạng thái. Thao tác này sẽ lưu trữ tất cả dữ liệu được ghi vào đó trong bộ nhớ, vì vậy, mọi dữ liệu sẽ bị mất bất cứ khi nào trình mô phỏng tắt. Nếu bạn đang chạy nhiều thử nghiệm dựa trên cùng một mã dự án, thì mỗi thử nghiệm có thể tạo ra dữ liệu có thể ảnh hưởng đến các thử nghiệm tiếp theo. Bạn có thể sử dụng bất kỳ phương thức nào sau đây để bỏ qua hành vi này:

  • Sử dụng mã dự án riêng biệt cho mỗi bài kiểm thử. Xin lưu ý rằng nếu chọn làm việc này, bạn sẽ cần gọi initializeTestEnvironment trong mỗi hoạt động kiểm thử; các quy tắc chỉ được tự động tải cho mã dự án mặc định.
  • Cấu trúc lại các kiểm thử để chúng không tương tác với dữ liệu đã ghi trước đó (ví dụ: sử dụng một tập hợp khác cho mỗi kiểm thử).
  • Xoá tất cả dữ liệu được ghi trong quá trình kiểm thử.

Việc thiết lập thử nghiệm rất phức tạp

Khi thiết lập kiểm thử, bạn có thể muốn sửa đổi dữ liệu theo cách mà Cloud Firestore Security Rules của bạn không thực sự cho phép. Nếu các quy tắc của bạn khiến quá trình thiết lập kiểm thử trở nên phức tạp, hãy thử dùng RulesTestEnvironment.withSecurityRulesDisabled trong các bước thiết lập để hoạt động đọc và ghi không kích hoạt lỗi PERMISSION_DENIED.

Sau đó, kiểm thử của bạn có thể thực hiện các thao tác với tư cách là người dùng đã xác thực hoặc chưa xác thực bằng cách sử dụng RulesTestEnvironment.authenticatedContextunauthenticatedContext tương ứng. Điều này cho phép bạn xác thực rằng Cloud Firestore Security Rules của bạn cho phép / từ chối đúng cách các trường hợp khác nhau.