ninjinkun's diary

ninjinkunの日記

Cloud FirestoreのSecurity RulesをCircleCIで自動テストする

この記事はFirebase Adventcalendar #2の13日目の記事です(もう12/17ですが、丁度書けそうなネタがあったので、空いてる日を見つけて埋めることにしました)。

Firabase Cloud Firestoreを使う場合、Security Rulesがアクセス制御全てのかなめと言えます。ここをミスるとデータが漏れて終わる、しかしその割に簡単に変更できてしまう。というわけで自動テストできると安心でしょう。

今回は先日公開した拙作のBlogFeedback(repo https://github.com/ninjinkun/blog-feedback-app/ )で用いているCircleCIによる自動テスト事例を紹介します。

なお、Security Rulesをテストするというアイデアは以下のエントリから頂きました。

エミュレーターの準備

Cloud Firestoreのエミュレーターとテスト用のライブラリが提供されているので、これを利用することでテストを記述・実行できます。エミュレーターの利用はこちらの記事を参考にしました。

firebase-toolsが入っていれば以下でインストールして

firebase setup:emulators:firestore

以下で立ち上げられます。

firebase serve --only firestore

テストの実装

公式のサンプルを参考にfirestore.rulesをテストしていきます。

サンプルではテストはmochaで記述されていますが、今回はcreate-react-appに同梱のJestで記述しました。

アプリ内で使っているRepositoryの関数を呼んで書き込みのテストをしているのと、他人のデータや本来書き込めない領域への書き込みが失敗するテストも記述しました。

src/tests/firebase-rulest.test.tsより一部抜粋

describe('/users/:user_id/blogs', () => { 
    it('save blog', async () => {
    const db = authedApp({ uid: 'ninjinkun' });
    // repositoryが内部で使っているdbを差し替える
    firebaseDB.mockReturnValue(db);

    await firebase.assertSucceeds(
    // repositoryに定義した関数で書き込めるか
        saveBlog(
        'ninjinkun',
        'https://ninjinkun.hatenablog.com/',
        "ninjinkun's diary",
        'https://ninjinkun.hatenablog.com/feed',
        'atom'
        )
    );
    });

    it('save undefined blog field', async () => {
    const db = authedApp({ uid: 'ninjinkun' });
    firebaseDB.mockReturnValue(db);
    const user = userRef('ninjinkun');
    // 存在しないフィールドに値をセット
    // rulesでちゃんとフィールドを制御しないと書き込めてしまうので注意
    await firebase.assertFails(user.set({ hoge: 'fuga' }));
    });
});

ローカルでテストが成功するようになったら、CircleCIの設定へ進みます。

CircleCIでの実行

CircleCIで実行する際のポイントとしては、エミュレータの実行にJavaが必要なため、OpenJDKとNode.js両方が入ったDocker Imageを選ぶことです。今回はとりあえず適当にcircleci/openjdk:11-jdk-sid-nodeを選択しました。

余談ですがCloud FunctionsのビルドにはNode.js v6 or v8が必要なため、デプロイ用のjobの方では circleci/node:8.14 を指定してあります。Cloud Functionsのテストも同時に行う場合は、OpenJDK + Node.js v8の組み合わせが入っているDocker Imageを用意する必要があるでしょう。

テストの実行前にエミュレーターのインストールと実行を追記する必要があります。

.circleci/config.ymlから一部抜粋(インストールしたエミュレーターをキャッシュできるとは思うのですが、そんなに時間がかかっているわけでもないのでさぼっています)

- run: yarn firebase setup:emulators:firestore
- run:
    command: yarn firebase serve --only firestore
    background: true

CIを実行すると以下の様にローカルと同じようにテストが実行されました。これで一安心。

CI実行結果から抜粋

#!/bin/bash -eo pipefail
yarn test:ci
yarn run v1.12.3
$ CI=true react-scripts test --env=jsdom
    PASS  src/__tests__/save-count-response.test.ts
    PASS  src/__tests__/firebase-rules.test.ts
    PASS  src/__tests__/App.test.tsx

Test Suites: 3 passed, 3 total
Tests:       10 passed, 10 total
Snapshots:   0 total
Time:        3.208s
Ran all test suites.
Done in 4.68s.

テストの速度

テストケース10個で以下の通りでした。エミュレーターの起動に18秒かかっているので、ここが一番遅いですね。とりあえず許容範囲内としていますが、他のテストでも時間がかかっているとストレスになるかもしれません。

f:id:ninjinkun:20181217172804p:plain