اختبار قواعد أمان Cloud Firestore

أثناء إنشاء تطبيقك، قد تحتاج إلى حظر الوصول إلى قاعدة بيانات Cloud Firestore. ومع ذلك، قبل إطلاق تطبيقك، ستحتاج إلى Cloud Firestore Security Rules أكثر دقة. باستخدام محاكي Cloud Firestore، يمكنك، بالإضافة إلى إنشاء نماذج أولية واختبار الميزات والسلوك العامين لتطبيقك، كتابة اختبارات الوحدات التي تتحقّق من سلوك Cloud Firestore Security Rules.

البدء السريع

بالنسبة إلى بعض حالات الاختبار الأساسية التي تتضمّن قواعد بسيطة، جرِّب نموذج البدء السريع.

فهم Cloud Firestore Security Rules

استخدِم Firebase Authentication وCloud Firestore Security Rules للمصادقة والتفويض والتحقّق من صحة البيانات بدون خادم عند استخدام مكتبات برامج العميل على الأجهزة الجوّالة والويب.

يتضمّن Cloud Firestore Security Rules جزأين:

  1. match عبارة تحدّد المستندات في قاعدة البيانات
  2. allow تعبير يتحكّم في الوصول إلى هذه المستندات

تتحقّق Firebase Authentication من بيانات اعتماد المستخدمين وتوفّر الأساس لأنظمة الوصول المستندة إلى المستخدمين والأدوار.

يتم تقييم كل طلب قاعدة بيانات من Cloud Firestore مكتبة برامج للعملاء على الأجهزة الجوّالة أو الويب بناءً على قواعد الأمان قبل قراءة أي بيانات أو كتابتها. إذا كانت القواعد ترفض الوصول إلى أيّ من مسارات المستندات المحدّدة، سيفشل الطلب بأكمله.

يمكنك الاطّلاع على مزيد من المعلومات عن Cloud Firestore Security Rules في مقالة البدء في استخدام Cloud Firestore Security Rules.

تثبيت المحاكي

لتثبيت محاكي Cloud Firestore، استخدِم Firebase CLI وشغِّل الأمر أدناه:

firebase setup:emulators:firestore

تشغيل المحاكي

ابدأ بتهيئة مشروع Firebase في دليل العمل. هذه الخطوة هي خطوة أولى شائعة عند استخدام واجهة سطر الأوامر (CLI) في Firebase.

firebase init

ابدأ المحاكي باستخدام الأمر التالي. سيتم تشغيل المحاكي إلى أن توقف العملية:

firebase emulators:start --only firestore

في كثير من الحالات، تريد بدء المحاكي وتشغيل مجموعة اختبارات ثم إيقاف المحاكي بعد تشغيل الاختبارات. يمكنك إجراء ذلك بسهولة باستخدام الأمر emulators:exec:

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

عند بدء المحاكي، سيحاول التشغيل على منفذ تلقائي (8080). يمكنك تغيير منفذ المحاكي من خلال تعديل القسم "emulators" في ملف firebase.json:

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

قبل تشغيل المحاكي

قبل البدء في استخدام المحاكي، يُرجى مراعاة ما يلي:

  • سيحمّل المحاكي في البداية القواعد المحدّدة في الحقل firestore.rules ضمن ملف firebase.json. يتوقّع هذا الخيار اسم ملف محلي يحتوي على Cloud Firestore Security Rules ويطبّق هذه القواعد على جميع المشاريع. في حال عدم تقديم مسار الملف المحلي أو استخدام طريقة loadFirestoreRules كما هو موضّح أدناه، سيتعامل المحاكي مع جميع المشاريع على أنّها تتضمّن قواعد مفتوحة.
  • في حين أنّ معظم حِزم تطوير البرامج (SDK) من Firebase تعمل مع المحاكيات مباشرةً، لا تتوافق مكتبة @firebase/rules-unit-testing إلا مع authالمحاكاةauth في "قواعد الأمان"، ما يسهّل إجراء اختبارات الوحدات. بالإضافة إلى ذلك، تتيح المكتبة بعض الميزات الخاصة بالمحاكي، مثل محو جميع البيانات، كما هو موضّح أدناه.
  • ستقبل المحاكيات أيضًا رموز Firebase Auth المميزة الخاصة بالإنتاج والمقدَّمة من خلال حِزم تطوير البرامج (SDK) الخاصة بالعملاء، وستقيّم القواعد وفقًا لذلك، ما يتيح ربط تطبيقك مباشرةً بالمحاكيات في اختبارات الدمج والاختبارات اليدوية.

تشغيل اختبارات الوحدات المحلية

إجراء اختبارات الوحدات المحلية باستخدام حزمة تطوير البرامج (SDK) لإصدار JavaScript 9

توزّع Firebase مكتبة لاختبار وحدات "قواعد الأمان" مع كلّ من حزمة JavaScript SDK الإصدار 9 والإصدارات الأقدم وحزمة SDK الإصدار 8. تختلف واجهات برمجة التطبيقات الخاصة بالمكتبة بشكل كبير. ننصحك باستخدام مكتبة الاختبار الإصدار 9، لأنّها أكثر سلاسة ولا تتطلّب الكثير من الإعدادات للاتصال بالمحاكيات، وبالتالي يمكنك تجنُّب الاستخدام غير المقصود لموارد الإنتاج بأمان. لضمان التوافق مع الإصدارات القديمة، سنواصل إتاحة مكتبة الاختبار الإصدار 8.

استخدِم وحدة @firebase/rules-unit-testing للتفاعل مع المحاكي الذي يتم تشغيله محليًا. إذا ظهرت لك أخطاء في المهلة أو أخطاء ECONNREFUSED، تحقَّق جيدًا من أنّ المحاكي يعمل بالفعل.

ننصحك بشدة باستخدام إصدار حديث من Node.js حتى تتمكّن من استخدام صيغة async/await. تتضمّن معظم السلوكيات التي قد تريد اختبارها دوالاً غير متزامنة، وقد تم تصميم وحدة الاختبار للعمل مع الرمز المستند إلى Promise.

تدرك مكتبة اختبار الوحدات لقواعد الإصدار 9 دائمًا المحاكيات ولا تتفاعل أبدًا مع موارد الإنتاج.

يمكنك استيراد المكتبة باستخدام عبارات الاستيراد النموذجية في الإصدار 9. على سبيل المثال:

import {
  assertFails,
  assertSucceeds,
  initializeTestEnvironment
} from "@firebase/rules-unit-testing"

// Use `const { … } = require("@firebase/rules-unit-testing")` if imports are not supported
// Or we suggest `const testing = require("@firebase/rules-unit-testing")` if necessary.

بعد الاستيراد، يتضمّن تنفيذ اختبارات الوحدات ما يلي:

  • إنشاء RulesTestEnvironment وإعداده مع عبارة تحث المستخدم على اتّخاذ إجراء initializeTestEnvironment
  • إعداد بيانات اختبار بدون تفعيل "القواعد"، وذلك باستخدام طريقة سهلة تتيح لك تجاوزها مؤقتًا، RulesTestEnvironment.withSecurityRulesDisabled
  • إعداد مجموعة الاختبارات وخطافات ما قبل الاختبار وما بعده لكل اختبار من خلال طلبات لتنظيف بيانات الاختبار والبيئة، مثل RulesTestEnvironment.cleanup() أو RulesTestEnvironment.clearFirestore()
  • تنفيذ حالات اختبار تحاكي حالات المصادقة باستخدام RulesTestEnvironment.authenticatedContext و RulesTestEnvironment.unauthenticatedContext

الطرق الشائعة والدوال المساعدة

يمكنك أيضًا الاطّلاع على طُرق الاختبار الخاصة بالمحاكي في حزمة تطوير البرامج (SDK) الإصدار 9.

initializeTestEnvironment() => RulesTestEnvironment

تعمل هذه الدالة على تهيئة بيئة اختبار لاختبار وحدات القواعد. يجب استدعاء هذه الدالة أولاً لإعداد الاختبار. يتطلّب التنفيذ الناجح أن تكون المحاكيات قيد التشغيل.

تقبل الدالة كائنًا اختياريًا يحدّد TestEnvironmentConfig، يمكن أن يتألف من معرّف مشروع وإعدادات ضبط المحاكي.

let testEnv = await initializeTestEnvironment({
  projectId: "demo-project-1234",
  firestore: {
    rules: fs.readFileSync("firestore.rules", "utf8"),
  },
});

RulesTestEnvironment.authenticatedContext({ user_id: string, tokenOptions?: TokenOptions }) => RulesTestContext

تنشئ هذه الطريقة RulesTestContext، الذي يتصرف مثل مستخدم مصادقة تمت المصادقة عليه. ستتضمّن الطلبات التي تم إنشاؤها من خلال السياق الذي تم عرضه رمزًا مميّزًا وهميًا للمصادقة. يمكنك اختياريًا تمرير عنصر يحدّد المطالبات المخصّصة أو عمليات التجاوز لحمولات رمز المصادقة.

استخدِم عنصر سياق الاختبار الذي تم عرضه في اختباراتك للوصول إلى أي مثيلات محاكي تم إعدادها، بما في ذلك تلك التي تم إعدادها باستخدام initializeTestEnvironment.

// Assuming a Firestore app and the Firestore emulator for this example
import { setDoc } from "firebase/firestore";

const alice = testEnv.authenticatedContext("alice", {  });
// Use the Firestore instance associated with this context
await assertSucceeds(setDoc(alice.firestore(), '/users/alice'), { ... });

RulesTestEnvironment.unauthenticatedContext() => RulesTestContext

تنشئ هذه الطريقة RulesTestContext، الذي يتصرف كعميل لم يسجّل الدخول من خلال خدمة "المصادقة". لن تتضمّن الطلبات التي يتم إنشاؤها من خلال السياق الذي تم عرضه رموزًا مميّزة خاصة بخدمة Firebase Auth.

استخدِم عنصر سياق الاختبار الذي تم عرضه في اختباراتك للوصول إلى أي مثيلات محاكي تم إعدادها، بما في ذلك تلك التي تم إعدادها باستخدام initializeTestEnvironment.

// Assuming a Cloud Storage app and the Storage emulator for this example
import { getStorage, ref, deleteObject } from "firebase/storage";

const alice = testEnv.unauthenticatedContext();

// Use the Cloud Storage instance associated with this context
const desertRef = ref(alice.storage(), 'images/desert.jpg');
await assertSucceeds(deleteObject(desertRef));

RulesTestEnvironment.withSecurityRulesDisabled()

تشغيل دالة إعداد اختبار باستخدام سياق يتصرف كما لو كانت "قواعد الأمان" غير مفعّلة

تتلقّى هذه الطريقة دالة ردّ اتصال تتلقّى سياق تجاوز قواعد الأمان وتعرض وعدًا. سيتم إتلاف السياق بعد أن يتم تنفيذ الوعد أو رفضه.

RulesTestEnvironment.cleanup()

تؤدي هذه الطريقة إلى إتلاف جميع RulesTestContexts التي تم إنشاؤها في بيئة الاختبار وتنظيف الموارد الأساسية، ما يتيح الخروج بشكل سليم.

لا تغيّر هذه الطريقة حالة المحاكيات بأي شكل من الأشكال. لإعادة ضبط البيانات بين الاختبارات، استخدِم طريقة محو البيانات الخاصة بمحاكي التطبيق.

assertSucceeds(pr: Promise<any>)) => Promise<any>

هذه دالة مساعدة لحالات الاختبار.

تؤكّد الدالة أنّ Promise الذي يغلّف عملية المحاكي سيتم تنفيذه بدون حدوث أي انتهاكات لقواعد الأمان.

await assertSucceeds(setDoc(alice.firestore(), '/users/alice'), { ... });

assertFails(pr: Promise<any>)) => Promise<any>

هذه دالة مساعدة لحالات الاختبار.

تؤكّد الدالة أنّ Promise المضمّن لعملية المحاكي سيتم رفضه بسبب انتهاك لقواعد الأمان.

await assertFails(setDoc(alice.firestore(), '/users/bob'), { ... });

طُرق خاصة بالمحاكي

يمكنك أيضًا الاطّلاع على طُرق الاختبار الشائعة ووظائف الأدوات المساعدة في الإصدار 9 من حزمة تطوير البرامج (SDK).

RulesTestEnvironment.clearFirestore() => Promise<void>

تؤدي هذه الطريقة إلى محو البيانات في قاعدة بيانات Firestore التي تنتمي إلى projectId الذي تم إعداده لمحاكي Firestore.

RulesTestContext.firestore(settings?: Firestore.FirestoreSettings) => Firestore;

تحصل هذه الطريقة على مثيل Firestore لسياق الاختبار هذا. يمكن استخدام مثيل حزمة تطوير البرامج (SDK) لعميل JavaScript من Firebase الذي تم عرضه مع واجهات برمجة التطبيقات لحزمة تطوير البرامج (SDK) للعميل (الإصدار 9 المعياري أو الإصدار 9 المتوافق).

تصوُّر عمليات تقييم القواعد

يتيح لك Cloud Firestore المحاكي عرض طلبات العملاء في واجهة مستخدم Emulator Suite، بما في ذلك تتبُّع التقييم لقواعد أمان Firebase.

افتح علامة التبويب Firestore > الطلبات لعرض تسلسل التقييم التفصيلي لكل طلب.

شاشة &quot;مراقبة طلبات محاكي Firestore&quot; تعرض تقييمات &quot;قواعد الأمان&quot;

إنشاء تقارير الاختبار

بعد إجراء مجموعة من الاختبارات، يمكنك الوصول إلى تقارير تغطية الاختبارات التي توضّح كيفية تقييم كل قاعدة من قواعد الأمان.

للحصول على التقارير، أرسِل طلب بحث إلى نقطة نهاية مكشوفة على المحاكي أثناء تشغيله. للحصول على نسخة متوافقة مع المتصفح، استخدِم عنوان URL التالي:

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

يؤدي ذلك إلى تقسيم قواعدك إلى عبارات وعبارات فرعية يمكنك تمرير مؤشر الماوس فوقها للحصول على مزيد من المعلومات، بما في ذلك عدد التقييمات والقيم التي تم إرجاعها. للحصول على نسخة JSON الأولية من هذه البيانات، أدرِج عنوان URL التالي في طلب البحث:

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

الاختلافات بين المحاكي وبيئة الإنتاج

  1. لست بحاجة إلى إنشاء مشروع Cloud Firestore بشكل صريح. ينشئ المحاكي تلقائيًا أي نسخة افتراضية يتم الوصول إليها.
  2. لا يعمل المحاكي Cloud Firestore مع مسار Firebase Authentication العادي. بدلاً من ذلك، وفّرنا في حزمة تطوير البرامج (SDK) الخاصة بالاختبار في Firebase الطريقة initializeTestApp() في مكتبة rules-unit-testing، والتي تأخذ الحقل auth. سيتصرف معرّف Firebase الذي تم إنشاؤه باستخدام هذه الطريقة كما لو أنّه تمّت مصادقته بنجاح كأي كيان تقدّمه. إذا أدخلت null، سيتم التعامل معها كمستخدم غير مصادق عليه (لن يتم تنفيذ قواعد auth != null، على سبيل المثال).

تحديد المشاكل المعروفة وحلّها

أثناء استخدام محاكي Cloud Firestore، قد تواجه المشاكل المعروفة التالية. اتّبِع الإرشادات أدناه لتحديد المشاكل وحلّها في حال ظهور سلوك غير منتظم. تمت كتابة هذه الملاحظات مع أخذ مكتبة اختبارات الوحدة لقواعد الأمان في الاعتبار، ولكن يمكن تطبيق الأساليب العامة على أي حزمة تطوير برامج (SDK) من Firebase.

السلوك التجريبي غير متّسق

إذا كانت اختباراتك تجتاز أحيانًا وتتعذّر أحيانًا أخرى، حتى بدون إجراء أي تغييرات على الاختبارات نفسها، قد تحتاج إلى التأكّد من ترتيبها بشكل صحيح. معظم التفاعلات مع المحاكي غير متزامنة، لذا يُرجى التأكّد من أنّ جميع الرموز غير المتزامنة مرتبة بشكل صحيح. يمكنك إصلاح التسلسل إما عن طريق ربط الوعود أو استخدام الترميز await بشكلٍ كبير.

على وجه الخصوص، راجِع العمليات غير المتزامنة التالية:

  • ضبط قواعد الأمان، على سبيل المثال، باستخدام initializeTestEnvironment
  • قراءة البيانات وكتابتها، باستخدام db.collection("users").doc("alice").get() مثلاً
  • بيانات التشغيل، بما في ذلك assertSucceeds وassertFails

لا يتم اجتياز الاختبارات إلا عند تحميل المحاكي للمرة الأولى

المحاكي له حالة. ويخزّن جميع البيانات التي تتم كتابتها في الذاكرة، وبالتالي يتم فقدان أي بيانات عند إيقاف المحاكي. إذا كنت تجري اختبارات متعددة على رقم تعريف المشروع نفسه، يمكن أن ينتج عن كل اختبار بيانات قد تؤثر في الاختبارات اللاحقة. يمكنك استخدام أي من الطرق التالية لتجاوز هذا السلوك:

  • استخدِم معرّفات مشاريع فريدة لكل اختبار. يُرجى العِلم أنّه في حال اختيار إجراء ذلك، عليك استدعاء initializeTestEnvironment كجزء من كل اختبار، إذ لا يتم تحميل القواعد تلقائيًا إلا لمعرّف المشروع التلقائي.
  • أعِد هيكلة اختباراتك كي لا تتفاعل مع البيانات المكتوبة سابقًا (على سبيل المثال، استخدِم مجموعة مختلفة لكل اختبار).
  • احذف جميع البيانات التي تمّت كتابتها أثناء الاختبار.

إعداد الاختبار معقّد جدًا

عند إعداد الاختبار، قد تحتاج إلى تعديل البيانات بطريقة لا تسمح بها Cloud Firestore Security Rules فعليًا. إذا كانت قواعدك تجعل عملية إعداد الاختبار معقّدة، جرِّب استخدام RulesTestEnvironment.withSecurityRulesDisabled في خطوات الإعداد، حتى لا تؤدي عمليات القراءة والكتابة إلى ظهور أخطاء PERMISSION_DENIED.

بعد ذلك، يمكن أن يجري الاختبار عمليات كمستخدم مصادَق عليه أو غير مصادَق عليه باستخدام RulesTestEnvironment.authenticatedContext وunauthenticatedContext على التوالي. يتيح لك ذلك التأكّد من أنّ Cloud Firestore Security Rules يسمح / يرفض الحالات المختلفة بشكل صحيح.