firebase security rule の テスト を jest + typescript で行う

● firebase security rule の テスト を jest + typescript で行う

・必要なnpmパッケージ @firebase/rules-unit-testingを追加する

yarn add @firebase/rules-unit-testing --dev

firestore.test.ts のサンプル

import fs from 'fs'
import * as testing from '@firebase/rules-unit-testing'
import * as http from 'http'

const projectId = '<FirebaseのプロジェクトID>'

// カバレッジレポートの出力
const COVERAGE_URL = `http://localhost:8080/emulator/v1/projects/${projectId}:ruleCoverage.html`
afterAll(async () => {
  const coverageFile = 'firestore-coverage.html'
  const fstream = fs.createWriteStream(coverageFile)
  await new Promise((resolve, reject) => {
    http.get(COVERAGE_URL, res => {
      res.pipe(fstream, { end: true })
      res.on('end', resolve)
      res.on('error', reject)
    })
  })
  console.log(`● カバレッジレポートを次のURLに出力しました\n${COVERAGE_URL}`)
})

describe('firestore', () => {
  let testEnv: testing.RulesTestEnvironment

  let userAuthor: testing.RulesTestContext
  let userMaintainer: testing.RulesTestContext
  let userDoesntHavePermission: testing.RulesTestContext
  let userUnauthenticated: testing.RulesTestContext

  beforeAll(async () => {
    testEnv = await testing.initializeTestEnvironment({
      projectId: projectId,
      firestore: {
        rules: fs.readFileSync('./firebase_emulator/firestore.rules', 'utf8'),
        host: 'localhost', // npx jest から実行する場合は必要(firebase emulators:exec からテストする場合は不要)
        port: 8080 // npx jest から実行する場合は必要(firebase emulators:exec からテストする場合は不要)
      }
    })

    userAuthor = testEnv.authenticatedContext('test__userAuthor')
    userMaintainer = testEnv.authenticatedContext('test__userMaintainer')
    userDoesntHavePermission = testEnv.authenticatedContext('test__userDoesntHavePermission')
    userUnauthenticated = testEnv.unauthenticatedContext()

    // テストデータ作成
    await testEnv.withSecurityRulesDisabled(async context => {
      // Client SDK からのルールが適用されない Firestore インスタンス。
      const noRuleDB = context.firestore()

      // 1. (/users/test__userAuthor)
      await noRuleDB.doc(`/users/test__userAuthor`).set({
        name: 'テストデータ(userAuthor)'
      })

      // 2. (/users/test__userMaintainer)
      await noRuleDB.doc(`/users/test__userMaintainer`).set({
        name: 'テストデータ(userMaintainer)',
        _permissions: ['/users/test__userAuthor/news']
      })

      // 3. (/users/test__userDoesntHavePermission)
      await noRuleDB.doc(`/users/test__userDoesntHavePermission`).set({
        name: 'テストデータ(userDoesntHavePermission)'
      })
    })
  })

  afterAll(async () => {
    // テストデータ削除
    await testEnv.withSecurityRulesDisabled(async context => {
      const noRuleDB = context.firestore()

      // 1. (/users/test__userAuthor)
      await noRuleDB.doc(`/users/test__userAuthor`).delete()

      // 2. (/users/test__userMaintainer)
      await noRuleDB.doc(`/users/test__userMaintainer`).delete()

      // 3. (/users/test__userDoesntHavePermission)
      await noRuleDB.doc(`/users/test__userDoesntHavePermission`).delete()
    })
  })

  test('「news/コレクション一覧の読み取り」: ログイン済みユーザ(本人)は読み取ることができる', async () => {
    const newList = userAuthor.firestore().collection('/users/test__userAuthor/news').get()
    await testing.assertSucceeds(newList)
  })

  test('「news/コレクション一覧の読み取り」: ログイン前ユーザ(本人)は読み取ることができない', async () => {
    const newList = userUnauthenticated.firestore().collection('/users/test__userAuthor/news').get()
    await testing.assertFails(newList)
  })

  test('「news/コレクション一覧の読み取り」: 権限を持たない本人以外のユーザは読み取ることができない', async () => {
    const newList = userDoesntHavePermission.firestore().collection('/users/test__userAuthor/news').get()
    await testing.assertFails(newList)
  })

  test('「news/コレクション一覧の読み取り」: 権限を持つ本人以外のユーザは読み取ることができる', async () => {
    const newList = userMaintainer.firestore().collection('/users/test__userAuthor/news').get()
    await testing.assertSucceeds(newList)
  })
})

● テストの実行

npx jest firestore.test.ts

FirestoreのカバレッジをGitHubActionsでいい感じに表示する - Qiita
rules-unit-testingがv2になったので変更点とともに紹介する

No.2242
10/31 19:24

edit