Closed22

FirestoreのSecurity RuleをLocalでテストする Step2

sakutarosakutaro

ドキュメントは、作成者じゃないと編集できないように。
ルールの追加

firestore.rules
    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); //追加
    }
sakutarosakutaro

テスト追加

test.js
  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
sakutarosakutaro

違うユーザーからの編集ができないことを確認
データ作成時のauthorIdをmyIdからtheirIdに変更

test.js
  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
sakutarosakutaro

モデレーターであれば、編集可能にする。
ルールの追加

firestore.rules
    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に。

firestore.rules
    function userIsModerator() {
      return request.auth.token.isModerator == true
    }
firestore.rules
    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、認証情報を定義しておく

test.js
const modId = "user_mod";
const modAuth = { uid: modId, email: 'mod@gmail.com', isModerator: true};

テスト追加

test.js
  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
sakutarosakutaro

このやり方は、カスタムクレームというやり方。
制限としては、
・1000バイト以下であること
・サーバーサイドで付与する必要がある。(クライアントでは付与できない)
・即時反映ではない。Firebaseのtoken有効期限(1時間?)分の遅延がある。
であってるかな。

なので、頻繁に権限が変更されるようなケースでは、カスタムクレームではなく
DBにフラグを保持しておくほうがいいっぽい。

sakutarosakutaro

各room内にpostsがあり、roomごとにmoderatorがいるというケース。
moderatorのフラグは頻繁に更新される可能性があるため、前述のカスタムクレームは適していない。
roomごとにroomMod(moderatorのuserId)を持たせておく形に。

動画から拝借

満たすべき条件は

  • postしたユーザーは自身のpostを編集できる
  • room内のユーザーは他ユーザーのpostは編集できない
  • roomModであるユーザーは、room内のpostを編集できる。

ルールの追加

firestore.rules
    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;
      }
    }

テスト追加

test.js
  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)
sakutarosakutaro

room内にモデレータが複数いるケースに対応する

ルール修正

firestore.rules
    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"]

test.js
  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 
sakutarosakutaro

そもそもの投稿に関するルールを作る。

また動画から拝借
request.resource.dataresource.dataがあるけど、

request.resource.data

requestが付いている通り、これから追加/編集するデータ。
あとで出てくるけど、編集時は変更されないフィールドも含まれていることに注意。

resource.data

すでにFirestoreに存在するデータ。

sakutarosakutaro

ルールの追加

firestore.rules
    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); //追加
    }

満たすべき条件は

  • ユーザー本人であれば、そのユーザーで投稿できる
  • 他のユーザーとしては投稿できない

テスト追加

test.js
  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
sakutarosakutaro

投稿データに、必須のフィールドを設定するケース。

単純に条件設定するなら
ルールはこんな感じになるが

firestore.rules
    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();
    }

もっと簡単にかけるので修正

firestore.rules
      function postHasAllRequiredFields() {
        return  (request.resource.data.keys().
          hasAll(["authorId", "visibility", "content", "headline"]));
      }

テスト追加

test.js
  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
sakutarosakutaro

必須フィールドを追加したことで、以前のテストが失敗する。
失敗するのは、

  • ユーザー本人であれば、そのユーザーで投稿できる

だけだが、

  • 他のユーザーとしては投稿できない

こちらのテストも、認証情報が異なる&必須フィールドがないためにassertFailsが満たされているので修正が必要。

テスト修正

test.js
  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
sakutarosakutaro

ルールを少しリファクタ

firestore.rules
      function postHasAllRequiredFields() {
        return  (request.resource.data.keys().
          hasAll(["authorId", "visibility", "content", "headline"]));
      }
firestore.rules
      function postHasAllRequiredFields() {
        let requiredFields = ["authorId", "visibility", "content", "headline"];
        return  (request.resource.data.keys().hasAll(requiredFields));
      }
sakutarosakutaro

投稿時に、意図しないフィールドが含まれていたら投稿できないようにする

ルール追加

firestore.rules
    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();  //修正
    }

テスト追加

test.js
  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
sakutarosakutaro

この入力項目のチェックは、今後よく使うと思うのでfunctionにする

firestore.rules
    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"]);
    }
sakutarosakutaro

投稿で編集できる項目を制限するケース
visibilitycontentのみに制限する。

firestore.rules
    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"]);
    }

テスト追加

test.js
  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つ失敗!!(デバッグ方法に続く。

sakutarosakutaro

状態を確認したいところをdebug()でwrapしてあげると、その値がlogに出力される。

firestore.rules
    function editOnlyChangesFields(allowedFields) {
      return debug(request.resource.data.keys()).hasOnly(allowedFields); //修正
    }

テスト実行

$ npm test

Logの末尾の方にkeyが出力されている。

firestore-debug.log
list_value {
  values {
    string_value: "authorId"
  }
  values {
    string_value: "content"
  }
  values {
    string_value: "headline"
  }
  values {
    string_value: "visibility"
  }
}
sakutarosakutaro

先のテストが失敗する理由は、
request.resource.data.keys()の中には、更新されるデータだけではなく、更新されない既存のデータも含まれるため。

動画ではこれを解決する古典的な方法として、全てのfieldの比較を行うことが紹介されて、
その後便利なmethodを利用する方法が紹介されている。もちろん後者で行う。

sakutarosakutaro

mapDiffを使うことによって、既存データからの変化したキーを抽出できる。
https://firebase.google.com/docs/reference/rules/rules.MapDiff

firestore.rules
    function editOnlyChangesFields(allowedFields) {
      let affectedKeys = request.resource.data.diff(resource.data).affectedKeys();
      return affectedKeys.hasOnly(allowedFields);
    }

最後のテストが通らないなぁと思ってたら、
postPathのpostsをpostと書いてしまうという凡ミス。。

修正して全てOK。

sakutarosakutaro

このチュートリアルで学んだもの。

カスタムクレーム

独自の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()
sakutarosakutaro

最終的なルール

firestore.rules
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();
      }
    }
  }
}
sakutarosakutaro

最終的なテスト

test.js

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});
  });

})
このスクラップは2021/01/24にクローズされました