「Unit testing security rules with the Firebase Emulator Suite」でセキュリティルールのtestを学ぶ
今更ながらFirebase Live 2020で発表された「Unit testing security rules with the Firebase Emulator Suite」を観た。
Firestoreのセキュリティルールは設定をミスると大事故になりうるのでローカルでセキュリティルールのtestができるようになったのはありがたい。
mochaと@firebase/testingを使ってシンプルなセキュリティルールのテストを作成した。
@firebase/testingについてはnpmのサイトを見るとdeprecatedになっており、今は@firebase/rules-unit-testingが推奨されているとのこと。
以下のサンプルコードでは@firebase/rules-unit-testingを使うようにした。
- yarn
yarn add -D mocha firebase-admin @firebase/rules-unit-testing
firestore.rules
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if false;
}
match /readonly/{docId} {
allow read: if true;
allow write: if false;
}
match /users/{userId} {
allow write: if (request.auth.uid == userId);
}
match /posts/{postId} {
allow read: if (resource.data.visibility == "public") || (resource.data.authorId == request.auth.uid);
}
}
}
test.js
const assert = require("assert");
const firebase = require("@firebase/rules-unit-testing");
const MY_PROJECT_ID = "my-social-app-d9e97";
const myId = "user_abc";
const theirId = "user_xyz";
const myAuth = { uid: myId, email: "abc@gmai.com" };
function getFirestore(auth) {
return firebase
.initializeTestApp({ projectId: MY_PROJECT_ID, auth: auth })
.firestore();
}
function getAdminFirestore() {
return firebase.initializeAdminApp({ projectId: MY_PROJECT_ID }).firestore();
}
beforeEach(async () => {
await firebase.clearFirestoreData({ projectId: MY_PROJECT_ID });
});
describe("Our social app", () => {
it("Understands basic addition", () => {
assert.strictEqual(2 + 2, 4);
});
it("Can read items in the read-only collection", async () => {
const db = getFirestore();
const testDoc = db.collection("readonly").doc("testDoc");
await firebase.assertSucceeds(testDoc.get());
});
it("Can't write to items in the read-only collection", async () => {
const db = getFirestore();
const testDoc = db.collection("readonly").doc("testDoc2");
await firebase.assertFails(testDoc.set({ foo: "bar" }));
});
it("Can write to a user document with the same ID as out user", async () => {
const db = getFirestore(myAuth);
const testDoc = db.collection("users").doc(myId);
await firebase.assertSucceeds(testDoc.set({ foo: "bar" }));
});
it("Can't write to a user document with the different ID as out user", async () => {
const db = getFirestore(myAuth);
const testDoc = db.collection("users").doc(theirId);
await firebase.assertFails(testDoc.set({ foo: "bar" }));
});
it("Can read posts marked public", async () => {
const db = getFirestore();
const testQuery = db
.collection("posts")
.where("visibility", "==", "public");
await firebase.assertSucceeds(testQuery.get());
});
it("Can query personal posts", async () => {
const db = getFirestore(myAuth);
const testQuery = db.collection("posts").where("authorId", "==", myId);
await firebase.assertSucceeds(testQuery.get());
});
it("Can't query all posts", async () => {
const db = getFirestore();
const testQuery = db.collection("posts");
await firebase.assertFails(testQuery.get());
});
it("Can read a single public post", async () => {
const admin = getAdminFirestore();
const postId = "public_post";
const setupDoc = admin.collection("posts").doc(postId);
await setupDoc.set({ auhorId: theirId, visibility: "public" });
const db = getFirestore(myAuth);
const testRead = db.collection("posts").doc(postId);
await firebase.assertSucceeds(testRead.get());
});
it("Can't read a private post belonging to the user", async () => {
const admin = getAdminFirestore();
const postId = "private_post";
const setupDoc = admin.collection("posts").doc(postId);
await setupDoc.set({ auhorId: theirId, visibility: "private" });
const db = getFirestore(myAuth);
const testRead = db.collection("posts").doc(postId);
await firebase.assertFails(testRead.get());
});
});
after(async () => {
await firebase.clearFirestoreData({ projectId: MY_PROJECT_ID });
});