Menguji Aturan Keamanan Cloud Firestore

Saat membuat aplikasi, Anda mungkin ingin mengunci akses ke database Cloud Firestore. Namun, sebelum memulai, Anda memerlukan Aturan Keamanan Cloud Firestore yang lebih tertata. Dengan emulator Cloud Firestore, Anda dapat menulis pengujian unit yang akan memeriksa perilaku Aturan Keamanan Cloud Firestore Anda.

Panduan memulai

Untuk beberapa kasus pengujian dasar dengan aturan sederhana, coba JavaScript quickstart atau TypeScript quickstart.

Memahami Aturan Keamanan Cloud Firestore

Terapkan Firebase Authentication dan Aturan Keamanan Cloud Firestore untuk autentikasi otorisasi, serta validasi data tanpa server saat Anda menggunakan library klien seluler dan web. .

Aturan Keamanan Cloud Firestore mencakup dua bagian:

  1. Pernyataan match yang mengidentifikasi dokumen dalam database Anda.
  2. Ekspresi allow yang mengontrol akses ke dokumen tersebut.

Firebase Authentication memverifikasi kredensial pengguna dan memberikan landasan untuk sistem akses berbasis pengguna dan peran.

Sebelum membaca atau menulis data, setiap permintaan database dari library klien seluler/web Cloud Firestore dievaluasi terhadap aturan keamanan Anda. Apabila aturan tersebut menolak akses ke salah satu lokasi dokumen yang ditentukan, seluruh permintaan akan gagal.

Pelajari Aturan Keamanan Cloud Firestore lebih lanjut di bagian Memulai Aturan Keamanan Cloud Firestore.

Menginstal emulator

Untuk menginstal emulator Cloud Firestore, gunakan Firebase CLI dan jalankan perintah di bawah:

firebase setup:emulators:firestore

Menjalankan emulator

Mulai emulator menggunakan perintah berikut. Emulator akan berjalan hingga Anda menghentikan prosesnya:

firebase emulators:start --only firestore

Dalam sebagian besar kasus, Anda dapat memulai emulator, menjalankan serangkaian pengujian, lalu menghentikan emulator setelah pengujian berjalan. Anda dapat melakukannya dengan mudah menggunakan perintah emulators:exec:

firebase emulators:exec --only firestore "./my-test-script.sh"

Ketika perintah mulai dijalankan, emulator akan mencoba berjalan pada port default (8080). Anda dapat mengubah port emulator dengan mengubah bagian "emulators" pada file firebase.json Anda:

{
  // ...
  "emulators": {
    "firestore": {
      "port": "YOUR_PORT"
    }
  }
}

Sebelum menjalankan emulator

Sebelum Anda mulai menggunakan emulator, perhatikan hal-hal berikut:

  • Pertama, emulator akan memuat aturan yang ditentukan dalam kolom firestore.rules file firebase.json Anda. Emulator mengharapkan nama file lokal yang berisi Aturan Keamanan Cloud Firestore dan menerapkan aturan tersebut untuk semua project. Jika Anda tidak memberikan lokasi file lokal atau menggunakan metode loadFirestoreRules seperti yang dijelaskan di bawah, emulator akan memperlakukan semua project sebagai aturan terbuka.
  • Meskipun banyak SDK yang berfungsi dengan emulator, hanya modul @firebase/testing Node.js yang mendukung penulisan fiktif auth di Aturan Keamanan, sehingga pengujian unit jauh lebih mudah. Selain itu, modul ini mendukung beberapa fitur khusus emulator seperti mengosongkan semua data, sebagaimana yang tercantum di bawah ini.
  • Emulator juga akan menerima token Firebase Auth produksi yang disediakan melalui SDK Klien dan mengevaluasi aturan yang sesuai, agar aplikasi Anda dapat terhubung langsung ke emulator dalam integrasi dan pengujian manual.

Menjalankan pengujian lokal

Gunakan modul @firebase/testing untuk berinteraksi dengan emulator yang berjalan secara lokal. Jika Anda menemukan error ECONNREFUSED atau waktu tunggu, periksa kembali apakah emulator sedang berjalan atau tidak.

Kami sangat menyarankan penggunaan Node.js versi terbaru agar Anda dapat menggunakan notasi async/await. Hampir semua perilaku yang mungkin ingin Anda uji melibatkan fungsi asinkron, dan modul pengujian dirancang untuk digunakan dengan kode berbasis Promise.

Modul ini menampilkan metode berikut:

  • initializeTestApp({ projectId: <name>, auth: <auth> }) => FirebaseApp

    Metode ini menampilkan aplikasi Firebase yang diinisialisasi sesuai dengan project ID dan variabel autentikasi yang ditentukan dalam opsi. Gunakan metode ini untuk membuat aplikasi yang diautentikasi sebagai pengguna khusus untuk digunakan dalam pegujian.

     firebase.initializeTestApp({
       projectId: "my-test-project",
       auth: { uid: "alice", email: "alice@example.com" }
     });
    
  • initializeAdminApp({ projectId: <name> }) => FirebaseApp

    Metode ini menampilkan aplikasi Firebase admin yang diinisialisasi. Aplikasi ini melewati aturan keamanan saat melakukan pembacaan dan penulisan. Gunakan metode ini untuk membuat aplikasi yang diautentikasi sebagai admin guna menyiapkan status untuk pengujian.

    firebase.initializeAdminApp({ projectId: "my-test-project" });
    
  • apps() => [FirebaseApp] Metode ini menampilkan semua aplikasi pengujian dan admin yang saat ini diinisialisasi. Gunakan ini untuk membersihkan aplikasi di antara pengujian atau setelah pengujian.

     Promise.all(firebase.apps().map(app => app.delete()))
    
  • loadFirestoreRules({ projectId: <name>, rules: <rules> }) => Promise

    Metode ini mengirim aturan ke database yang berjalan secara lokal. Perlu objek yang menentukan aturan sebagai string. Gunakan metode ini untuk menetapkan aturan database Anda.

     firebase.loadFirestoreRules({
       projectId: "my-test-project",
       rules: fs.readFileSync("/path/to/firestore.rules", "utf8")
     });
    
  • assertFails(pr: Promise) => Promise

    Metode ini menampilkan promise yang ditolak jika input berhasil atau yang berhasil jika input ditolak. Gunakan metode ini untuk menegaskan jika pembacaan atau penulisan database gagal.

    firebase.assertFails(app.firestore().collection("private").doc("super-secret-document").get());
    
  • assertSucceeds(pr: Promise) => Promise

    Metode ini menampilkan promise yang berhasil jika input berhasil dan ditolak jika input ditolak. Gunakan metode ini untuk menegaskan jika pembacaan atau penulisan database berhasil:

    firebase.assertSucceeds(app.firestore().collection("public").doc("test-document").get());
    
  • clearFirestoreData({ projectId: <name> }) => Promise

    Metode ini menghapus semua data yang terkait dengan project tertentu dalam instance Firestore yang dijalankan secara lokal. Gunakan metode ini untuk menghapus setelah pengujian.

    firebase.clearFirestoreData({
     projectId: "my-test-project"
    });
    

Menghasilkan laporan pengujian

Setelah menjalankan serangkaian pengujian, Anda dapat mengakses laporan cakupan pengujian yang menunjukkan evaluasi dari setiap aturan keamanan Anda.

Untuk mendapatkan laporan, buat kueri untuk endpoint yang terekspos pada emulator selagi endpoint ini berjalan. Untuk versi yang cocok dengan browser, gunakan URL berikut:

http://localhost:8080/emulator/v1/projects/<project_id>:ruleCoverage.html

URL ini memerinci aturan Anda ke dalam ekspresi dan subekspresi yang dapat Anda klik untuk mengetahui informasi lebih lanjut, termasuk jumlah evaluasi dan nilai yang ditampilkan. Untuk versi JSON mentah data ini, sertakan URL berikut pada kueri Anda:

http://localhost:8080/emulator/v1/projects/<project_id>:ruleCoverage

Perbedaan antara emulator dan produksi

  1. Anda tidak harus secara eksplisit membuat project Cloud Firestore. Emulator secara otomatis membuat setiap instance yang diakses.
  2. Emulator Cloud Firestore tidak berfungsi dengan aliran Firebase Authentication normal. Sebagai gantinya, dalam Firebase Test SDK, kami telah menyediakan metode initializeTestApp() dalam modul pengujian, yang menempati kolom auth. Pengendali Firebase yang dibuat menggunakan metode ini akan berperilaku seolah-olah telah berhasil diautentikasi sebagai entity apa pun yang Anda berikan. Jika Anda meneruskan null, pengendali tersebut akan berperilaku sebagai pengguna tak terautentikasi (misalnya, aturan auth != null akan gagal).

Mengatasi masalah yang diketahui

Saat menggunakan emulator Cloud Firestore, Anda mungkin mengalami masalah yang diketahui di bawah ini. Ikuti panduan berikut untuk memecahkan masalah perilaku tidak teratur yang Anda alami. Catatan ini ditulis dengan Firebase Test SDK, tetapi pendekatan umum berlaku untuk setiap Firebase SDK.

Perilaku pengujian tidak konsisten

Jika pengujian Anda sesekali lolos dan gagal, bahkan tanpa perubahan apa pun pada pengujian itu sendiri, Anda mungkin perlu memastikan apakah pengujian ini telah diurutkan dengan benar atau belum. Sebagian besar interaksi dengan emulator bersifat asinkron, jadi periksa kembali apakah semua kode asinkron telah diurutkan dengan benar. Anda dapat memperbaiki urutan tersebut dengan membuat janji, atau menggunakan notasi await secara bebas.

Secara khusus, tinjau operasi asinkron berikut:

  • Menyetel aturan keamanan, dengan, misalnya, firebase.loadFirestoreRules.
  • Membaca dan menulis data, dengan, misalnya, db.collection("users").doc("alice").get().
  • Pernyataan operasional, termasuk firebase.assertSucceeds dan firebase.assertFails.

Pengujian hanya diteruskan saat pertama kali Anda memuat emulator

Emulator bersifat stateful. Emulator menyimpan semua data yang ditulis ke dalam memori, sehingga data apa pun akan hilang setiap kali emulator nonaktif. Jika Anda menjalankan beberapa pengujian terhadap project id yang sama, setiap pengujian dapat menghasilkan data yang mungkin memengaruhi pengujian berikutnya. Anda dapat menggunakan salah satu metode berikut untuk mengabaikan perilaku ini:

  • Gunakan project ID unik untuk setiap pengujian.
  • Ubah struktur pengujian Anda agar tidak berinteraksi dengan data tertulis sebelumnya (misalnya, gunakan koleksi yang berbeda untuk setiap pengujian).
  • Hapus semua data tertulis selama pengujian.

Penyiapan pengujian sangat rumit

Anda dapat menguji skenario yang tidak diizinkan berdasarkan Aturan Keamanan Cloud Firestore Anda. Misalnya, sulit untuk menguji apakah pengguna yang tidak diautentikasi dapat mengedit data, karena Anda tidak dapat mengedit data sebagai pengguna yang tidak diautentikasi.

Jika aturan Anda memperumit penyiapan pengujian, coba gunakan klien yang diotorisasi admin untuk mengabaikan aturan. Anda dapat melakukannya dengan firebase.initializeAdminApp. Membaca dan menulis dari klien yang diotorisasi oleh admin mengabaikan aturan dan tidak memicu error PERMISSION_DENIED.