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

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

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

توزّع Firebase مكتبة اختبار وحدات قواعد الأمان مع كلّ من الإصدار 9 من حزمة تطوير البرامج (SDK) لـ JavaScript والإصدار 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

الطرق الشائعة والدوالّ الخدمية

اطّلِع أيضًا على طرق الاختبار الخاصة بالمحاكي في حزمة تطوير البرامج (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>

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

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

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 يسمح / يرفض الحالات المختلفة بشكلٍ صحيح.