اختبار قواعد أمان 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 في دليل العمل. هذه هي الخطوة الأولى الشائعة عند استخدام Firebase CLI.

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 كما هو موضّح أدناه، سيتعامل المحاكي مع جميع المشاريع على أنّها تتضمّن قواعد مفتوحة.
  • في حين أنّ معظم حِزم Firebase SDK تعمل مع المحاكيات مباشرةً، لا تدعم مكتبة @firebase/rules-unit-testing إلا محاكاة auth في "قواعد الأمان"، ما يسهّل اختبارات الوحدة إلى حد كبير. بالإضافة إلى ذلك، تدعم المكتبة بعض الميزات الخاصة بالمحاكي، مثل محو جميع البيانات، كما هو موضّح أدناه.
  • ستقبل المحاكيات أيضًا رموز مصادقة Firebase للإنتاج المقدَّمة من خلال حِزم SDK للعميل وتقيِّم القواعد وفقًا لذلك، ما يسمح بربط تطبيقك مباشرةً بالمحاكيات في الاختبارات التكاملية واليدوية.

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

تشغيل اختبارات الوحدة المحلية باستخدام الإصدار 9 من JavaScript SDK

توزّع Firebase مكتبة اختبارات الوحدة لقواعد الأمان مع الإصدار 9 من JavaScript SDK والإصدار 8 من SDK. تختلف واجهات برمجة التطبيقات في المكتبة اختلافًا كبيرًا. ننصحك باستخدام مكتبة الاختبارات في الإصدار 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.

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

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

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، الذي يتصرف مثل مستخدم مصادقة تم التحقق منه في ميزة "مصادقة Firebase". ستتضمّن الطلبات التي يتم إنشاؤها من خلال السياق الذي يتم عرضه رمز مصادقة وهميًا. يمكنك اختياريًا تمرير كائن يحدّد المطالبات المخصّصة أو عمليات الإلغاء لحمولات رمز مصادقة Firebase.

استخدِم كائن سياق الاختبار الذي يتم عرضه في اختباراتك للوصول إلى أيّ من مثيلات المحاكي التي تم إعدادها، بما في ذلك تلك التي تم إعدادها باستخدام 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().doc('/users/alice'), { ... });

RulesTestEnvironment.unauthenticatedContext() => RulesTestContext

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

استخدِم كائن سياق الاختبار الذي يتم عرضه في اختباراتك للوصول إلى أيّ من مثيلات المحاكي التي تم إعدادها، بما في ذلك تلك التي تم إعدادها باستخدام 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>

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

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

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

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

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

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

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

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

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

RulesTestEnvironment.clearFirestore() => Promise<void>

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

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

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

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

يتيح لك محاكي Cloud Firestore تصوُّر طلبات العميل في واجهة مستخدم مجموعة أدوات المحاكاة، بما في ذلك تتبُّع التقييم لقواعد أمان 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 العادي. بدلاً من ذلك، في Firebase Test SDK، وفّرنا طريقة initializeTestApp() في مكتبة rules-unit-testing، التي تأخذ حقل auth. سيتصرف مقبض Firebase الذي تم إنشاؤه باستخدام هذه الطريقة كما لو تم التحقق من هويته بنجاح كأي كيان تقدّمه. إذا مرّرت null، سيتصرف المحاكي كمستخدم لم يتم التحقق من هويته (auth != null ستفشل القواعد، على سبيل المثال).

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

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

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

إذا كانت اختباراتك تجتاز وتفشل أحيانًا، حتى بدون إجراء أي تغييرات على الاختبارات نفسها، قد تحتاج إلى التحقّق من أنّها متسلسلة بشكل صحيح. معظم التفاعلات مع المحاكي غير متزامنة، لذا تحقَّق جيدًا من أنّ جميع الرموز غير المتزامنة متسلسلة بشكل صحيح. يمكنك إصلاح التسلسل من خلال ربط الوعود أو استخدام الرمز 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 تسمح بحالات مختلفة أو ترفضها بشكل صحيح.