اختبار قواعد أمان 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.

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

إجراء اختبارات الوحدة المحلية

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

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

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

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

تدرك مكتبة اختبار الوحدات v9 (القواعد) v9 دائمًا الأدوات المحاكية ولا تمس موارد الإنتاج أبدًا.

يمكنك استيراد المكتبة باستخدام عبارات الاستيراد المُركّبة في الإصدار 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()

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

تأخذ هذه الطريقة دالة ردّ اتصال تستخدِم سياق Security-Rules-bypassing وتُعرِض وعدًا. وسيتم إتلاف السياق بمجرد حل الوعد / رفضه.

RulesTestEnvironment.cleanup()

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

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

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

هذه وظيفة أداة لحالة الاختبار.

تؤكِّد الدالة أنّه سيتم حلّ عملية المحاكي المقدَّمة بجهاز Promise بدون انتهاك لقواعد الأمان.

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 لسياق الاختبار هذا. يمكن استخدام مثيل حزمة تطوير البرامج (SDK) لبرنامج Firebase JS العميل المعروض مع واجهات برمجة تطبيقات حزمة تطوير البرامج (SDK) للبرنامج (الإصدار 9 من حزمة تطوير البرامج (SDK) المتوافقة مع الوحدات أو الإصدار 9 من حزمة تطوير البرامج (SDK) المتوافقة مع الإصدارات السابقة).

عرض تقييمات القواعد بصريًا

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

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

أداة &quot;مراقبة طلبات محاكي Firestore&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 يسمح بمختلف الحالات أو يرفضها بشكل صحيح.