اختبار قواعد أمان 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 Security Rules" في مقالة بدء استخدام "Cloud Firestore Security Rules".

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

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

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 Auth المميزة للإنتاج المقدمة من خلال حزم SDK للعميل وتقييم القواعد وفقًا لذلك، مما يسمح بربط تطبيقك مباشرة بالمحاكيات في التكامل والاختبارات اليدوية.

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

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

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

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

ننصح بشدة باستخدام إصدار حديث من Node.js حتى تتمكّن من استخدام ترميز async/await. Almost all of the behavior you might want to test involves asynchronous functions, and the testing module is designed to work with Promise-based code.

تدرك مكتبة اختبار وحدة القواعد 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.

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

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

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

هذه دالة خدمية لإطار الاختبار.

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

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