FirestoreのSecurity RuleをLocalでテストする Step2
前回
の続き。
この動画に沿って。
ドキュメントは、作成者じゃないと編集できないように。
ルールの追加
match /posts/{postId} {
allow read: if (resource.data.visibility =="public") ||
(resource.data.authorId == request.auth.uid);
allow update: if(resource.data.authorId == request.auth.uid); //追加
}
テスト追加
it("Allows a user to edit their own posts", async() => {
const postId = "post_123";
const admin = getAdminFirestore();
await admin.collection("posts").doc(postId).set({content: "before", authorId: myId});
const db = getFirestore(myAuth);
const testDoc = db.collection("posts").doc(postId);
await firebase.assertSucceeds(testDoc.update({content: "after"}));
});
テスト
$ npm test
✓ Allows a user to edit their own posts
違うユーザーからの編集ができないことを確認
データ作成時のauthorIdをmyId
からtheirId
に変更
it("Doesn't allow a user to edit somebody elses posts", async() => {
const postId = "post_123";
const admin = getAdminFirestore();
await admin.collection("posts").doc(postId).set({content: "before", authorId: theirId});
const db = getFirestore(myAuth);
const testDoc = db.collection("posts").doc(postId);
await firebase.assertFails(testDoc.update({content: "after"}));
});
テスト
✓ Doesn't allow a user to edit somebody elses posts
モデレーターであれば、編集可能にする。
ルールの追加
match /posts/{postId} {
allow read: if (resource.data.visibility =="public") ||
(resource.data.authorId == request.auth.uid);
allow update: if(resource.data.authorId == request.auth.uid) ||
request.auth.token.isModerator == true; //追加
}
可読性を上げるために、functionに。
function userIsModerator() {
return request.auth.token.isModerator == true
}
match /posts/{postId} {
allow read: if (resource.data.visibility =="public") ||
(resource.data.authorId == request.auth.uid);
allow update: if(resource.data.authorId == request.auth.uid) ||
userIsModerator(); //修正
}
モデレーターのユーザーID、認証情報を定義しておく
const modId = "user_mod";
const modAuth = { uid: modId, email: 'mod@gmail.com', isModerator: true};
テスト追加
it("Allows a moderator to edit somebody elses posts", async() => {
const postId = "post_123";
const admin = getAdminFirestore();
await admin.collection("posts").doc(postId).set({content: "before", authorId: theirId});
const db = getFirestore(modAuth); //モデレータの認証情報を利用
const testDoc = db.collection("posts").doc(postId);
await firebase.assertSucceeds(testDoc.update({content: "after"}));
});
テスト
✓ Allows a moderator to edit somebody elses posts
このやり方は、カスタムクレームというやり方。
制限としては、
・1000バイト以下であること
・サーバーサイドで付与する必要がある。(クライアントでは付与できない)
・即時反映ではない。Firebaseのtoken有効期限(1時間?)分の遅延がある。
であってるかな。
なので、頻繁に権限が変更されるようなケースでは、カスタムクレームではなく
DBにフラグを保持しておくほうがいいっぽい。
各room内にpostsがあり、roomごとにmoderatorがいるというケース。
moderatorのフラグは頻繁に更新される可能性があるため、前述のカスタムクレームは適していない。
roomごとにroomMod(moderatorのuserId)
を持たせておく形に。
動画から拝借
満たすべき条件は
- postしたユーザーは自身のpostを編集できる
- room内のユーザーは他ユーザーのpostは編集できない
- roomModであるユーザーは、room内のpostを編集できる。
ルールの追加
match /rooms/{roomId} {
match /posts/{postId} {
allow update: if(resource.data.authorId == request.auth.uid) ||
get(/databases/$(database)/documents/rooms/$(roomId)).data.roomMod == request.auth.uid;
}
}
テスト追加
it("Allows a user to edit their own room posts", async() => {
const postPath = "/rooms/room_abc/posts/post_123"
const admin = getAdminFirestore();
await admin.doc(postPath).set({content: "before", authorId: myId});
const db = getFirestore(myAuth);
const testDoc = db.doc(postPath);
await firebase.assertSucceeds(testDoc.update({content: "after"}));
});
it("Won't a user to edit somebody elses room posts", async() => {
const postPath = "/rooms/room_abc/posts/post_123"
const admin = getAdminFirestore();
await admin.doc(postPath).set({content: "before", authorId: theirId});
const db = getFirestore(myAuth);
const testDoc = db.doc(postPath);
await firebase.assertFails(testDoc.update({content: "after"}));
});
it("Allows a room mod to edit another person's room posts", async() => {
const roomPath = "rooms/room_abc";
const postPath = `${roomPath}/posts/post_123`;
const admin = getAdminFirestore();
await admin.doc(roomPath).set({topic: "Unit testers", roomMod: myId});
await admin.doc(postPath).set({content: "before", authorId: theirId});
const db = getFirestore(myAuth);
const testDoc = db.doc(postPath);
await firebase.assertSucceeds(testDoc.update({content: "after"}));
});
テスト
✓ Allows a user to edit their own room posts (55ms)
✓ Won't a user to edit somebody elses room posts (58ms)
✓ Allows a room mod to edit another person's room posts (58ms)
room内にモデレータが複数いるケースに対応する
ルール修正
match /rooms/{roomId} {
function userIsRoomMod() {
return (request.auth.uid in (get(/databases/$(database)/documents/rooms/$(roomId)).data.roomMods))
}
match /posts/{postId} {
allow update: if(resource.data.authorId == request.auth.uid) ||
userIsRoomMod();
}
}
テスト修正
roomMod: myId
-> roomMods: [myId, "dummy_user"]
it("Allows a room mod to edit another person's room posts", async() => {
const roomPath = "rooms/room_abc";
const postPath = `${roomPath}/posts/post_123`;
const admin = getAdminFirestore();
await admin.doc(roomPath).set({topic: "Unit testers", roomMods: [myId, "dummy_user"]});
await admin.doc(postPath).set({content: "before", authorId: theirId});
const db = getFirestore(myAuth);
const testDoc = db.doc(postPath);
await firebase.assertSucceeds(testDoc.update({content: "after"}));
});
テスト
✓ Allows a room mod to edit another person's room posts
そもそもの投稿に関するルールを作る。
また動画から拝借
request.resource.data
とresource.data
があるけど、
request.resource.data
requestが付いている通り、これから追加/編集するデータ。
あとで出てくるけど、編集時は変更されないフィールドも含まれていることに注意。
resource.data
すでにFirestoreに存在するデータ。
ルールの追加
match /posts/{postId} {
allow read: if (resource.data.visibility =="public") ||
(resource.data.authorId == request.auth.uid);
allow update: if(resource.data.authorId == request.auth.uid) ||
userIsModerator();
allow create: if(request.resource.data.authorId == request.auth.uid); //追加
}
満たすべき条件は
- ユーザー本人であれば、そのユーザーで投稿できる
- 他のユーザーとしては投稿できない
テスト追加
it("Allows a user to create a post when they list themselves as the author", async() => {
const postPath = "posts/post_123";
const db = getFirestore(myAuth);
const testDoc = db.doc(postPath);
await firebase.assertSucceeds(testDoc.set({authorId: myId, content: "lorem ipsum"}));
});
it("Doesn't let a user to create a post when they list somebody else as the author", async() => {
const postPath = "posts/post_123";
const db = getFirestore(myAuth);
const testDoc = db.doc(postPath);
await firebase.assertFails(testDoc.set({authorId: theirId, content: "lorem ipsum"}));
});
テスト
✓ Allows a user to create a post when they list themselves as the author
✓ Doesn't let a user to create a post when they list somebody else as the author
投稿データに、必須のフィールドを設定するケース。
単純に条件設定するなら
ルールはこんな感じになるが
match /posts/{postId} {
function postHasAllRequiredFields() {
return request.resource.data.authorId != null &&
request.resource.data.visibility != null &&
request.resource.data.content != null &&
request.resource.data.headline != null &&
...
}
...
allow create: if(request.resource.data.authorId == request.auth.uid) &&
postHasAllRequiredFields();
}
もっと簡単にかけるので修正
function postHasAllRequiredFields() {
return (request.resource.data.keys().
hasAll(["authorId", "visibility", "content", "headline"]));
}
テスト追加
it("Can create a post with all required field", async() => {
const postPath = "posts/post_123";
const db = getFirestore(myAuth);
const testDoc = db.doc(postPath);
await firebase.assertSucceeds(testDoc.set({authorId: myId, content: "lorem ipsum",
visibility: "public", headline: "headline"}));
});
it("Can't create a post missing some required fields", async() => {
const postPath = "posts/post_123";
const db = getFirestore(myAuth);
const testDoc = db.doc(postPath);
await firebase.assertFails(testDoc.set({authorId: myId, headline: "headline"}));
});
テスト
✓ Can create a post with all required field
✓ Can't create a post missing some required fields
必須フィールドを追加したことで、以前のテストが失敗する。
失敗するのは、
- ユーザー本人であれば、そのユーザーで投稿できる
だけだが、
- 他のユーザーとしては投稿できない
こちらのテストも、認証情報が異なる&必須フィールドがないためにassertFailsが満たされているので修正が必要。
テスト修正
it("Allows a user to create a post when they list themselves as the author", async() => {
const postPath = "posts/post_123";
const db = getFirestore(myAuth);
const testDoc = db.doc(postPath);
await firebase.assertSucceeds(testDoc.set({authorId: myId, content: "lorem ipsum",
visibility: "public", headline: "headline"})); //修正
});
it("Doesn't let a user to create a post when they list somebody else as the author", async() => {
const postPath = "posts/post_123";
const db = getFirestore(myAuth);
const testDoc = db.doc(postPath);
await firebase.assertFails(testDoc.set({authorId: theirId, content: "lorem ipsum",
visibility: "public", headline: "headline"})); //修正
});
テスト
✓ Allows a user to create a post when they list themselves as the author
✓ Doesn't let a user to create a post when they list somebody else as the author
ルールを少しリファクタ
function postHasAllRequiredFields() {
return (request.resource.data.keys().
hasAll(["authorId", "visibility", "content", "headline"]));
}
function postHasAllRequiredFields() {
let requiredFields = ["authorId", "visibility", "content", "headline"];
return (request.resource.data.keys().hasAll(requiredFields));
}
投稿時に、意図しないフィールドが含まれていたら投稿できないようにする
ルール追加
match /posts/{postId} {
function postHasAllRequiredFields() {
let requiredFields = ["authorId", "visibility", "content", "headline"];
return (request.resource.data.keys().hasAll(requiredFields));
}
function postHasOnlyAllowsFields() { //追加
let requiredFields = ["authorId", "visibility", "content", "headline"];
let optionalFields = ["photo", "tags", "location"];
let allFields = requiredFields.concat(optionalFields);
return (request.resource.data.keys().hasOnly(allFields));
}
allow read: if (resource.data.visibility =="public") ||
(resource.data.authorId == request.auth.uid);
allow update: if(resource.data.authorId == request.auth.uid) ||
userIsModerator();
allow create: if(request.resource.data.authorId == request.auth.uid) &&
postHasAllRequiredFields() && postHasOnlyAllowsFields(); //修正
}
テスト追加
it("Can create a post with all required and optional fields", async() => {
const postPath = "posts/post_123";
const db = getFirestore(myAuth);
const testDoc = db.doc(postPath);
await firebase.assertSucceeds(testDoc.set({authorId: myId, content: "lorem ipsum",
visibility: "public", headline: "headline", location: "Japan",
tags: ["screencast", "firebase"], photo: "url_goes_hear"}));
});
it("Can't create a post with an unapproved fields", async() => {
const postPath = "posts/post_123";
const db = getFirestore(myAuth);
const testDoc = db.doc(postPath);
await firebase.assertFails(testDoc.set({authorId: myId, content: "lorem ipsum",
visibility: "public", headline: "headline", location: "Japan",
not_allowed: true}));
});
テスト
✓ Can create a post with all required and optional fields
✓ Can't create a post with an unapproved fields
この入力項目のチェックは、今後よく使うと思うのでfunctionにする
function documentFieldCheckOut(requiredFields, optionalFields) {
let allFields = requiredFields.concat(optionalFields);
return request.resource.data.keys().hasAll(requiredFields) &&
request.resource.data.keys().hasOnly(allFields);
}
match /posts/{postId} {
allow read: if (resource.data.visibility =="public") ||
(resource.data.authorId == request.auth.uid);
allow update: if(resource.data.authorId == request.auth.uid) ||
userIsModerator();
allow create: if(request.resource.data.authorId == request.auth.uid) &&
documentFieldCheckOut(["authorId", "visibility", "content", "headline"],
["photo", "tags", "location"]);
}
投稿で編集できる項目を制限するケース
visibility
とcontent
のみに制限する。
function editOnlyChangesFields(allowedFields) {
return request.resource.data.keys().hasOnly(allowedFields);
}
match /posts/{postId} {
allow read: if (resource.data.visibility =="public") ||
(resource.data.authorId == request.auth.uid);
allow update: if((resource.data.authorId == request.auth.uid) || userIsModerator())
&& editOnlyChangesFields(["visibility", "content"]); //修正
allow create: if(request.resource.data.authorId == request.auth.uid) &&
documentFieldCheckOut(["authorId", "visibility", "content", "headline"],
["photo", "tags", "location"]);
}
テスト追加
it("Can edit a post with allowed fields", async() => {
const postPath = "posts/post_123";
const admin = getAdminFirestore();
await admin.doc(postPath).set({content: "before_content", authorId: myId,
headline: "before_headline", visibility: "public"});
const db = getFirestore(myAuth);
const testDoc = db.doc(postPath);
await firebase.assertSucceeds(testDoc.update({content: "after_content"}));
});
テスト
1) Allows a user to edit their own posts
2) Allows a moderator to edit somebody elses posts
3) Can edit a post with allowed fields
3つ失敗!!(デバッグ方法に続く。
状態を確認したいところをdebug()でwrapしてあげると、その値がlogに出力される。
function editOnlyChangesFields(allowedFields) {
return debug(request.resource.data.keys()).hasOnly(allowedFields); //修正
}
テスト実行
$ npm test
Logの末尾の方にkeyが出力されている。
list_value {
values {
string_value: "authorId"
}
values {
string_value: "content"
}
values {
string_value: "headline"
}
values {
string_value: "visibility"
}
}
先のテストが失敗する理由は、
request.resource.data.keys()の中には、更新されるデータだけではなく、更新されない既存のデータも含まれるため。
動画ではこれを解決する古典的な方法として、全てのfieldの比較を行うことが紹介されて、
その後便利なmethodを利用する方法が紹介されている。もちろん後者で行う。
mapDiffを使うことによって、既存データからの変化したキーを抽出できる。
function editOnlyChangesFields(allowedFields) {
let affectedKeys = request.resource.data.diff(resource.data).affectedKeys();
return affectedKeys.hasOnly(allowedFields);
}
最後のテストが通らないなぁと思ってたら、
postPathのpostsをpostと書いてしまうという凡ミス。。
修正して全てOK。
このチュートリアルで学んだもの。
カスタムクレーム
独自のstatusを認証情報にのせることができる
get関数
他のドキュメントへアクセスできる。フルパスが必要。
Custom Function
ルール内の処理を関数にできる。
Lists と sets
hasOnlyやhasAll, inを使うことで配列の中の値を評価できる。
let
letを使うことで、ルール内関数の変数を定義できる。
debug()
debugでWrapすることで、rule内に記述された値やオブジェクトをlogに出力できる。
出力先はfirestore-debug.log
mapDiffs
変化したkeyを抽出できる。
request.resource.data.diff(resource.data).affectedKeys()
最終的なルール
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
function userIsModerator() {
return request.auth.token.isModerator == true
}
function documentFieldCheckOut(requiredFields, optionalFields) {
let allFields = requiredFields.concat(optionalFields);
return request.resource.data.keys().hasAll(requiredFields) &&
request.resource.data.keys().hasOnly(allFields);
}
function editOnlyChangesFields(allowedFields) {
let affectedKeys = request.resource.data.diff(resource.data).affectedKeys();
return affectedKeys.hasOnly(allowedFields);
}
// lock down the db
match /{document=**} {
allow read: if false;
allow 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);
allow update: if((resource.data.authorId == request.auth.uid) || userIsModerator())
&& editOnlyChangesFields(["visibility", "content"]);
allow create: if(request.resource.data.authorId == request.auth.uid) &&
documentFieldCheckOut(["authorId", "visibility", "content", "headline"],
["photo", "tags", "location"]);
}
match /rooms/{roomId} {
function userIsRoomMod() {
return (request.auth.uid in (get(/databases/$(database)/documents/rooms/$(roomId)).data.roomMods))
}
match /posts/{postId} {
allow update: if(resource.data.authorId == request.auth.uid) ||
userIsRoomMod();
}
}
}
}
最終的なテスト
const assert = require('assert');
const firebase = require('@firebase/rules-unit-testing');
const MY_PROJECT_ID = "project-id";
const myId = "user_abc";
const theirId = "user_xyz";
const modId = "user_mod";
const myAuth = { uid: myId, email: 'user_abc@gmail.com'};
const modAuth = { uid: modId, email: 'mod@gmail.com', isModerator: true};
function getFirestore(auth) {
return firebase.initializeTestApp({projectId: MY_PROJECT_ID, auth: auth}).firestore();
}
function getAdminFirestore(auth) {
return firebase.initializeAdminApp({projectId: MY_PROJECT_ID}).firestore();
}
beforeEach (async() => {
await firebase.clearFirestoreData({projectId: MY_PROJECT_ID});
});
describe("Our test app", ()=> {
it("Understands basic addition", () => {
assert.strictEqual(2+2, 4);
});
it("Can read items in the read-only collection", async() => {
const db = getFirestore(null);
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(null);
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 our 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 our 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(null);
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(myAuth);
const testQuery = db.collection("posts");
await firebase.assertFails(testQuery.get());
});
it("Can query a single public post", async() => {
const admin = getAdminFirestore();
const postId = "public_post";
const setupDoc = admin.collection("posts").doc(postId);
await setupDoc.set({authorId: theirId, visibility: "public"});
const db = getFirestore();
const readQuery = db.collection("posts").doc(postId);
await firebase.assertSucceeds(readQuery.get());
});
it("Can 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({authorId: myId, visibility: "private"});
const db = getFirestore(myAuth);
const readQuery = db.collection("posts").doc(postId);
await firebase.assertSucceeds(readQuery.get());
});
it("Can't read a private post belonging to the another user", async() => {
const admin = getAdminFirestore();
const postId = "private_post";
const setupDoc = admin.collection("posts").doc(postId);
await setupDoc.set({authorId: theirId, visibility: "private"});
const db = getFirestore(myAuth);
const readQuery = db.collection("posts").doc(postId);
await firebase.assertFails(readQuery.get());
});
it("Allows a user to edit their own posts", async() => {
const postId = "post_123";
const admin = getAdminFirestore();
await admin.collection("posts").doc(postId).set({content: "before", authorId: myId});
const db = getFirestore(myAuth);
const testDoc = db.collection("posts").doc(postId);
await firebase.assertSucceeds(testDoc.update({content: "after"}));
});
it("Doesn't allow a user to edit somebody elses posts", async() => {
const postId = "post_123";
const admin = getAdminFirestore();
await admin.collection("posts").doc(postId).set({content: "before", authorId: theirId});
const db = getFirestore(myAuth);
const testDoc = db.collection("posts").doc(postId);
await firebase.assertFails(testDoc.update({content: "after"}));
});
it("Allows a moderator to edit somebody elses posts", async() => {
const postId = "post_123";
const admin = getAdminFirestore();
await admin.collection("posts").doc(postId).set({content: "before", authorId: theirId});
const db = getFirestore(modAuth);
const testDoc = db.collection("posts").doc(postId);
await firebase.assertSucceeds(testDoc.update({content: "after"}));
});
it("Allows a user to edit their own room posts", async() => {
const postPath = "/rooms/room_abc/posts/post_123"
const admin = getAdminFirestore();
await admin.doc(postPath).set({content: "before", authorId: myId});
const db = getFirestore(myAuth);
const testDoc = db.doc(postPath);
await firebase.assertSucceeds(testDoc.update({content: "after"}));
});
it("Won't a user to edit somebody elses room posts", async() => {
const postPath = "/rooms/room_abc/posts/post_123"
const admin = getAdminFirestore();
await admin.doc(postPath).set({content: "before", authorId: theirId});
const db = getFirestore(myAuth);
const testDoc = db.doc(postPath);
await firebase.assertFails(testDoc.update({content: "after"}));
});
it("Allows a room mod to edit another person's room posts", async() => {
const roomPath = "rooms/room_abc";
const postPath = `${roomPath}/posts/post_123`;
const admin = getAdminFirestore();
await admin.doc(roomPath).set({topic: "Unit testers", roomMods: [myId, "dummy_user"]});
await admin.doc(postPath).set({content: "before", authorId: theirId});
const db = getFirestore(myAuth);
const testDoc = db.doc(postPath);
await firebase.assertSucceeds(testDoc.update({content: "after"}));
});
it("Allows a user to create a post when they list themselves as the author", async() => {
const postPath = "posts/post_123";
const db = getFirestore(myAuth);
const testDoc = db.doc(postPath);
await firebase.assertSucceeds(testDoc.set({authorId: myId, content: "lorem ipsum",
visibility: "public", headline: "headline"}));
});
it("Doesn't let a user to create a post when they list somebody else as the author", async() => {
const postPath = "posts/post_123";
const db = getFirestore(myAuth);
const testDoc = db.doc(postPath);
await firebase.assertFails(testDoc.set({authorId: theirId, content: "lorem ipsum",
visibility: "public", headline: "headline"}));
});
it("Can create a post with all required field", async() => {
const postPath = "posts/post_123";
const db = getFirestore(myAuth);
const testDoc = db.doc(postPath);
await firebase.assertSucceeds(testDoc.set({authorId: myId, content: "lorem ipsum",
visibility: "public", headline: "headline"}));
});
it("Can't create a post missing some required fields", async() => {
const postPath = "posts/post_123";
const db = getFirestore(myAuth);
const testDoc = db.doc(postPath);
await firebase.assertFails(testDoc.set({authorId: myId, headline: "headline"}));
});
it("Can create a post with all required and optional fields", async() => {
const postPath = "posts/post_123";
const db = getFirestore(myAuth);
const testDoc = db.doc(postPath);
await firebase.assertSucceeds(testDoc.set({authorId: myId, content: "lorem ipsum",
visibility: "public", headline: "headline", location: "Japan",
tags: ["screencast", "firebase"], photo: "url_goes_hear"}));
});
it("Can't create a post with an unapproved fields", async() => {
const postPath = "posts/post_123";
const db = getFirestore(myAuth);
const testDoc = db.doc(postPath);
await firebase.assertFails(testDoc.set({authorId: myId, content: "lorem ipsum",
visibility: "public", headline: "headline", location: "Japan",
not_allowed: true}));
});
it("Can edit a post with allowed fields", async() => {
const postPath = "posts/post_123";
const admin = getAdminFirestore();
await admin.doc(postPath).set({content: "before_content", authorId: myId,
headline: "before_headline", visibility: "public"});
const db = getFirestore(myAuth);
const testDoc = db.doc(postPath);
await firebase.assertSucceeds(testDoc.update({content: "after_content"}));
});
after (async() => {
await firebase.clearFirestoreData({projectId: MY_PROJECT_ID});
});
})