🔥
firebase/rules-unit-testingで初期データを登録したいけどPERMISSION_DENIEDが出る
下記のような権限エラーが出た
FirebaseError: 7 PERMISSION_DENIED: Null value error. for 'create' @ L128
結論
- エミュレータは最初に firebase.json ファイルの firestore.rules フィールドで指定されたルールを読み込みます。
- 上記より、エミュレーター起動時に先にセキュリティールールが適用されているので、isAdmin等のユーザーロールを見てwriteを許可しているもの等は権限エラーになります。
-
firebase.initializeAdminApp
で同じprojectIdのAppのFirestoreのデータを書き込めば、セキュリティールールやAuthの設定を無視して書き込む事ができます。
セキュリティールール(抜粋)
match /users/{userId} {
allow read: if isAdmin() || isMyUser(userId);
allow write: if isAdmin() || isMyUser(userId);
}
firebaseTestingの設定用関数を作る
firebaseTesting.ts
import * as firebase from '@firebase/rules-unit-testing'
import fs from "fs"
import path from "path"
const filePath = path.join(__dirname, './firestore.rules')
const rules = fs.readFileSync(filePath, "utf8")
const projectId = `rules-test-${Date.now()}`;
type Auth = { uid: string; email?: string }
type DataValue = { [key: string ]: any }
type Data = { [ke: string]: DataValue }
const addMockData = async (data: Data) => {
const adminApp = firebase.initializeAdminApp({ projectId })
const adminDB = adminApp.firestore()
for (const key in data) {
const ref = adminDB.doc(key);
await ref.set(data[key]);
}
}
// データベース設定
export const setup = async (auth?: Auth, data?: Data) => {
const app = await firebase.initializeTestApp({
projectId,
auth
})
// モックデータが有ればAdminAppを使って流し込む
data && await addMockData(data)
// セキュリティールールの適用
await firebase.loadFirestoreRules({
projectId,
rules
})
const db = app.firestore();
return db;
}
// アプリの全削除
export const deleteAppAll = async () => {
await Promise.all(firebase.apps().map(app => app.delete()));
}
// データベースの初期化
export const clearDB = async () => {
await firebase.clearFirestoreData({ projectId });
}
使い方
firestoreRules.test.ts
import * as firebase from '@firebase/rules-unit-testing'
import { setup, deleteAppAll, clearDB } from './firebaseTesting'
const currentUserUid = 'current-user-uid';
const receiveUserUid = 'receive-user-uid';
const USER = 'users';
process.env.FIRESTORE_EMULATOR_HOST = 'localhost:8080'
describe('Firestore Security Rules', () => {
// テスト毎にデータの初期化
afterEach(async () => await clearDB())
// 全テストが完了したらappを全て削除
afterAll(async () => await deleteAppAll())
describe('ユーザー', () =>
test('読取り', async () => {
const currentUserPath = `${USER}/${currentUserUid}`;
const receiveUserPath = `${USER}/${receiveUserUid}`;
const auth = { uid: currentUserUid }
const data = {
[currentUserPath]: { name: 'ログインユーザーさん', role: 'admin' },
[receiveUserPath]: { name: '読み取られるユーザーさん', role: 'nomal' },
}
const db = await setup(auth, data);
const getFnc = db.doc(receiveCmsUserPath).get();
await firebase.assertSucceeds(getFnc);
})
})
})
参考
単体テストを作成する by firebase公式ドキュメント
エミュレータを使用したFirestoreセキュリティルールのテスト
Discussion