اختبار قواعد أمان 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 من حزمة تطوير البرامج (SDK) لـ JavaScript والإصدار 8 من حزمة تطوير البرامج (SDK). تختلف واجهات برمجة التطبيقات للمكتبة بشكل كبير. ننصح باستخدام مكتبة الاختبار v9، فهي أكثر بساطة وتتطلب إعدادًا أقل للاتصال بالمحاكيات، وبالتالي تتجنب بأمان الاستخدام غير المقصود لموارد الإنتاج. لضمان التوافق مع الأنظمة القديمة، سنواصل إتاحة مكتبة الاختبارات للإصدار 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، والذي يتصرف مثل مستخدم مصادق عليه في Authentication. ستتضمّن الطلبات التي يتم إنشاؤها من خلال السياق الذي تم عرضه رمزًا مميزًا وهميًا للمصادقة. يمكنك اختياريًا تمرير عنصر يحدّد المطالبات المخصّصة أو عمليات إلغاء حمولات الرموز المميّزة للمصادقة.

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

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

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

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

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 المعياري أو الإصدار 9 المتوافق).

عرض تقييمات القواعد

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

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