الاختبار السلبي لواجهات API

الاختبار السلبي لواجهات API (Negative API Testing) يرسل طلبات ينص عقد الواجهة على وجوب رفضها، ويتحقق من أن كلاً منها يُرفض رفضاً صحيحاً: برمز الحالة المناسب من فئة 4xx، وبجسم خطأ بالصيغة الموثقة، ومن دون أي تغيير في البيانات. إنه الاختبار السلبي الذي تعرفه، على مستوى لا يوجد فيه نموذج يوقف المستخدم: فأي عميل يستطيع أن يرسل أي شيء إلى نقطة النهاية (Endpoint).

مثال: تقبل POST /api/v1/cart/items قيمة quantity من ⁦1⁩ إلى ⁦10⁩. فالطلب الذي يحمل "quantity": 0 يجب أن يتلقى 422 مع جسم يسمّي quantity، ويجب أن يُظهر طلب لاحق GET /api/v1/cart أن السلة لم تتغير. أما 201 أو 500 أو 200 مع رسالة خطأ في الجسم فعيب.

لماذا يهم

تستدعي تطبيقاتُ الجوال وأنظمةُ الشركاء والسكربتاتُ الواجهةَ البرمجية من دون المرور بواجهة المستخدم، فيجب أن تفرض الواجهة كل قاعدة بنفسها. وحين تثق بمدخلاتها فإنها تخزّن بيانات تالفة، أو تنهار برمز 500، أو تسرّب تتبّع المكدس (Stack Trace)، أو تسمح للعميل بتعيين خصائص لا يجوز أن يتحكم فيها، مثل السعر. وتدرج قائمة OWASP API Security Top 10 لعام ⁦2023⁩ هذه الحالة الأخيرة تحت البند API3، أي خلل التفويض على مستوى خصائص الكائن (Broken Object Property Level Authorization).

واستجابات الأخطاء واجهةٌ أيضاً: فالعملاء يقرؤون الرمز وجسم الخطأ ليقرروا ما يعرضونه وهل يعيدون المحاولة، فتغيير صيغة الخطأ يكسرهم كما يكسرهم تغيير استجابة النجاح. أما المختبِر فيمنحه العقد نتيجة متوقعة دقيقة لكل رفض، فتكون اختبارات API السلبية دقيقة وسهلة الأتمتة.

فئات الطلبات غير الصالحة والرمز الذي تستحقه كل منها

صمّم اختبارات API السلبية كما صمّمت الأقسام غير الصالحة في تقسيم التكافؤ (Equivalence Partitioning): فئة واحدة في كل اختبار، وسائر أجزاء الطلب صالحة، حتى يشير الفشل إلى قاعدة واحدة. وتأتي الرموز من RFC 9110 ومن درس واجهات REST، وحيث يترك HTTP الخيار، مثل 400 أو 422 للقيم غير الصالحة، يحسم العقد.

  • صياغة تالفة: جسم مقطوع، أو فاصلة زائدة في النهاية، أو جسم فارغ مُعلَن أنه JSON ← 400 Bad Request.
  • حقول ناقصة: احذف كل حقل إلزامي بدوره، ثم أرسله بالقيمة null وبالنص الفارغ ""، وقد تعامل الواجهة كلاً منها معاملة مختلفة ← 400 أو 422 مع ذكر الحقل.
  • أنواع خاطئة: "2" بدلاً من 2، و2.5 لعدد صحيح، ومصفوفة بدلاً من كائن ← 400 أو 422، لا تحويل صامت.
  • قيم غير صالحة: قيمة خارج القائمة المسموحة (enum)، أو التاريخ 2026-02-30، أو خاصية لا يجوز للعميل تعيينها مثل "price": 0.01 ← 422 أو 400، أو تُتجاهل إن نص العقد على ذلك، لكنها لا تُطبَّق أبداً.
  • نوع وسائط خاطئ: Content-Type: text/plain مع جسم JSON ← 415 Unsupported Media Type.
  • طريقة خاطئة: PUT على مسار لا يدعم إلا GET وPOST ← 405 Method Not Allowed مع ترويسة Allow. أما الطريقة المدعومة التي لا يحق لهذا المستدعي استخدامها فجوابها 403، كما في درس اختبار التفويض.
  • مصادقة غير صالحة: بيانات اعتماد غائبة أو تالفة أو منتهية الصلاحية ← 401 مع WWW-Authenticate، كما في درس مصادقة واجهات API.
  • مورد غير موجود: GET /api/v1/cart/items/99999 ← 404 Not Found.
ثماني فئات من الطلبات غير الصالحة إلى واجهة السلة، والرمز الذي يشترطه عقدها لكل فئة، وجواب خاطئ يكشف عيباً في تحقق الواجهة من المدخلات.
فئة الطلب غير الصالحمثالالرمز المتوقعجواب خاطئ يُبلَّغ عنه
صياغة تالفة{"productId": 42,400نجاح500: خطأ التحليل غير معالَجفشل
حقل ناقص{"quantity": 2}422المؤشر #/productIdنجاح500، أو 201 مع تخزين قيمة فارغةفشل
نوع خاطئ"quantity": "2"422نجاح201: حُوِّل النص بصمتفشل
قيمة غير صالحة"quantity": 11422نجاح201: الحد ⁦10⁩ يُفحص في واجهة المستخدم فقطفشل
نوع وسائط خاطئContent-Type: text/plain415نجاح500، أو صفحة خطأ HTMLفشل
طريقة خاطئةPUT /api/v1/cart/items405مع Allow: GET, POSTنجاح404 أو 500 بلا ترويسة Allowفشل
مصادقة غير صالحةبلا ترويسة Authorization401مع WWW-Authenticateنجاح422 مع أخطاء الحقول: قُرئ الجسم أولاًفشل
مورد غير موجودGET /api/v1/cart/items/99999404نجاح200 بجسم فارغ، أو 500فشل

تتبع رموز الحالة RFC 9110. يستخدم هذا العقد 400 للصياغة التالفة و422 للقيم غير الصالحة، وقد يستخدم عقد آخر 400 للحالتين. وفي كل صف يتبع جسم الخطأ الصيغة الموثقة ولا تتغير أي بيانات.

جرّبها: مولّد الاختبارات السلبية جرّبها: مولّد بيانات اختبار API

اختبار الحدود في واجهات API

يعمل تحليل القيم الحدّية على الواجهة البرمجية كما يعمل على النموذج، لكن الحدود مكتوبة في مخطط (Schema) مستند OpenAPI. فالكلمات المفتاحية minimum وmaximum وminLength وmaxLength وminItems وmaxItems في JSON Schema (ومعها صيغها الحصرية) تعطي كلٌّ منها حدّاً والقيم المجاورة له. ولمعاملات الاستعلام حدود أيضاً: فحجم الصفحة limit من ⁦1⁩ إلى ⁦100⁩ يعطي ⁦0⁩ و⁦1⁩ و⁦100⁩ و⁦101⁩. وتعرّف RFC 9110 الرمز 422 لمحتوى الطلب، لذا تجيب واجهات كثيرة عن معامل استعلام غير صالح بالرمز 400 حتى لو استخدمت 422 لقيم الجسم، كما في المثال أدناه؛ والعقد هو الذي يحسم.

ويجب أن يحدد العقد أيضاً ما يحدث بعد الحد: رفض الطلب، أو قصّ القيمة إلى الحد (Clamping)، كأن تُعامَل limit=500 على أنها ⁦100⁩. وكلاهما قابل للاختبار، أما السلوك غير المحدد فسؤال للفريق. وبعض الحدود لا يُكتب في المخطط أصلاً: فالبند OWASP API4:2023، أي الاستهلاك غير المقيّد للموارد (Unrestricted Resource Consumption)، يذكر حدوداً غائبة مثل عدد السجلات في الصفحة وأقصى حجم للملف المرفوع.

  • الأعداد الصحيحة الكبيرة: تنبّه RFC 8259 إلى أن البرمجيات المختلفة لا تتفق تماماً على قيم أعداد JSON الصحيحة إلا بين -9007199254740991 و9007199254740991 (2^53 - 1)، لذا قد يقرّب عميل JavaScript معرّفاً أكبر من ذلك بصمت.
  • حجم الجسم: تحت الحد الموثق مباشرةً وفوقه مباشرةً، مع توقع 413 Content Too Large (RFC 9110) فوقه، في بيئة يُسمح لك بإرسال حمل كهذا إليها.
quantity (body):  { "type": "integer", "minimum": 1, "maximum": 10 }
  0 -> 422      1 -> 201      10 -> 201      11 -> 422

limit (query):    integer, 1..100, default 20
  0 -> 400      1 -> 200      100 -> 200     101 -> 400 (reject)
                                             101 -> 200, at most 100 items (clamp)

جرّبها: مولّد القيم الحدّية جرّبها: مولّد حالات اختبار من OpenAPI

عقد استجابة الخطأ: تفاصيل المشكلة وفق RFC 9457

يحتاج الاختبار السلبي إلى جسم خطأ متوقع، لا إلى رمز حالة فقط. وتعرّف RFC 9457، أي تفاصيل المشكلة لواجهات HTTP (Problem Details for HTTP APIs) التي حلّت محل RFC 7807، صيغة لذلك: نوع الوسائط application/problem+json وكائن JSON بخمسة عناصر قياسية. ويجوز للواجهة إضافة عناصر امتداد (Extension Members)، مثل قائمة بأخطاء الحقول، وعلى العملاء تجاهل الامتدادات التي لا يعرفونها. وفي المثال أدناه، كما في المواصفة نفسها، لكل خطأ في حقلٍ عنصرُ pointer، وقيمته JSON Pointer (RFC 6901) يشير إلى الحقل في جسم الطلب، مكتوباً بصيغة جزء URI (Fragment) هكذا: #/quantity.

ولا يُلزَم فريقك باستخدام RFC 9457، لكن يجب أن تتبع كل نقاط النهاية صيغة واحدة موثقة. واشمل الأخطاء التي لا تنتجها شيفرة التطبيق نفسها، مثل 404 من الموجّه (Router) أو 413 من الوكيل (Proxy)، فهي الأكثر احتمالاً لأن تصل بصيغة أخرى، وأحياناً صفحةَ HTML.

  • type: معرّف URI لنوع المشكلة، وعليه يبني العملاء والاختبارات قراراتهم لا على صياغة title. وإذا غاب فقيمته about:blank.
  • title: ملخص قصير للنوع، يبقى نفسه في كل مرة تقع فيها المشكلة، إلا عند الترجمة.
  • status: إن وُجد فهو مطابق للرمز في سطر الحالة، كما تشترط RFC 9457.
  • detail: شرح لهذه الحالة بعينها، مكتوب ليساعد العميل على تصحيح طلبه لا لتنقيح أخطاء الخادم.
  • instance: عنصر اختياري، مرجع URI لهذه الحالة بعينها، يفيد في العثور عليها في السجلات.
  • لا تفاصيل داخلية: لا تتبّع مكدس ولا SQL ولا مسار ملف ولا رقم إصدار، ولا كلمة مرور أو رمز وصول (Token) يُعاد في الاستجابة. وتحذّر RFC 9457 من هذا التسريب، ويعدّه البند OWASP API8:2023 من سوء الإعدادات الأمنية (Security Misconfiguration).
HTTP/1.1 422 Unprocessable Content
Content-Type: application/problem+json

{
  "type": "https://api.example.com/problems/validation-error",
  "title": "The request contains invalid fields.",
  "status": 422,
  "detail": "quantity must be between 1 and 10.",
  "errors": [
    { "pointer": "#/quantity", "detail": "must be between 1 and 10" }
  ]
}

جرّبها: مولّد استجابات خطأ وهمية جرّبها: مدقّق استجابات API

مثال تطبيقي: اختبارات سلبية لإضافة عنصر إلى السلة

العقد: يتطلب POST /api/v1/cart/items رمز Bearer وجسم JSON فيه productId (عدد صحيح لا يقل عن ⁦1⁩) وquantity (عدد صحيح من ⁦1⁩ إلى ⁦10⁩). ويدعم المسار GET وPOST، وتُرفض الخصائص غير المعروفة. وتستخدم الأخطاء application/problem+json: الرمز 400 للجسم التالف و422 للقيم غير الصالحة. ويبدأ كل اختبار بسلة فارغة، والمنتج ⁦42⁩ موجود.

يكسر كل اختبار قاعدة واحدة، وبعده يجب أن يعيد GET /api/v1/cart سلة فارغة: فالرمز الصحيح مع عنصر مخزَّن يبقى اختباراً فاشلاً.

  • الاختبار NA-04 واحد من أربعة اختبارات حدّية للحقل quantity: فالقيمة "quantity": 0 يجب أن تعيد 422 أيضاً، وشريكاه الإيجابيان "quantity": 1 و"quantity": 10 يجب أن يعيدا 201.
  • يكسر NA-09 قاعدتين عن قصد ليختبر الأسبقية: اتفق الفريق على فحص بيانات الاعتماد قبل الجسم، فلا يعرف المستدعي المجهول قواعد التحقق أبداً.
  • إذا أعاد NA-05 الرمز 201 وسعر البند ⁦1.00⁩ ريال، فهذا عيب حرج (Critical): العميل هو من حدّد السعر.
ID     Request (one rule broken)                      Expected
NA-01  {"productId": 42, "quantity": 2,               400, problem+json
NA-02  {"quantity": 2}                                422, pointer #/productId
NA-03  {"productId": 42, "quantity": "2"}             422, pointer #/quantity
NA-04  {"productId": 42, "quantity": 11}              422, pointer #/quantity
NA-05  {"productId": 42, "quantity": 2, "price": 1}   422, pointer #/price
NA-06  valid body, Content-Type: text/plain           415
NA-07  PUT /api/v1/cart/items                         405, Allow: GET, POST
NA-08  valid body, no Authorization header            401, WWW-Authenticate: Bearer
NA-09  no Authorization header, {"quantity": 0}       401, not 422

جرّبها: مختبِر REST API جرّبها: منشئ طلبات API

أخطاء شائعة

  • الاكتفاء بالتحقق من أن «الرمز من فئة 4xx»: فالرمز 400 حيث ينص العقد على 401 يخفي فحص مصادقة يعمل متأخراً.
  • فحص الاستجابة دون البيانات: أجابت الواجهة بالرمز 422 لكن العنصر حُفظ رغم ذلك. أتبِع كل كتابة مرفوضة بعملية قراءة.
  • كسر عدة قواعد في طلب واحد، فلا يتضح أيّ قاعدة طبّقتها الواجهة، إلا في اختبار أسبقية مقصود.
  • اختبار أخطاء شيفرة التطبيق وحدها ونسيان أخطاء الموجّه وإطار العمل والوكيل.
  • قبول الرمز 500 بحجة أن الطلب «رُفض على الأقل»: فرمز من فئة 5xx لخطأ من العميل يعني أن التحقق مفقود.

من منظور ضمان الجودة

اطلب عقد الأخطاء أثناء التنقيح (Refinement)، قبل بناء نقطة النهاية: الرمز لكل فئة، وصيغة الجسم، وهل تُرفض الخصائص غير المعروفة أم تُتجاهل، وهل تُرفض القيم التي تتجاوز الحد أم تُقصّ إليه، وبأي ترتيب تُفحص بيانات الاعتماد والطريقة ونوع الوسائط والجسم. واحرص على أن تُكتب الإجابات في مستند OpenAPI الذي يقرؤه المطورون وفرق العملاء والاختبارات. والحالات المولَّدة منه بداية جيدة، لكنك أنت من يختار الفئات والحدود المهمة بحسب المخاطر.

تناسب اختبارات API السلبية الأتمتة المعتمدة على البيانات (Data-Driven): صف لكل فئة أو قيمة حدّية فيه الطلب والرمز المتوقع وtype المتوقع، ويعمل مع كل تغيير. ويحتاج تقرير الخطأ إلى الطلب بالضبط (وأمر cURL مثالي لذلك)، والاستجابة كاملة، وقاعدة العقد المخالَفة، وأي أثر جانبي. وtitle بصياغة رديئة عيب ثانوي (Minor)، أما البيانات التالفة أو سعر يحدده العميل أو تتبّع مكدس مسرَّب فأمر خطير. ولا ترسل طلبات غير صالحة إلا إلى أنظمة تملكها أو لديك إذن مكتوب باختبارها.

تمرين عملي

قيّم ست استجابات خطأ من واجهة مواعيد

المطلوب

لواجهة حجز مواعيد في عيادة العقدُ أدناه، ولصق زميلك نتائج ستة اختبارات سلبية. اكتب لكل منها الاستجابة المتوقعة، وقرّر هل نجح الاختبار، واكتب عنوان تقرير خطأ من سطر واحد لكل فشل.

ثم اكتب اختبارات الحدود الناقصة للحقلين date وnotes مع الرمز المتوقع لكل منها.

المادة
Contract: POST /api/v1/appointments    (the path supports GET and POST)
Auth:     Bearer token, checked before the body
Body:     doctorId  integer >= 1, required
          date      "YYYY-MM-DD", from today to 60 days ahead, required
          notes     string, at most 500 characters, optional
Errors:   application/problem+json; malformed body 400; invalid values 422
Today:    2026-11-01; tests #1 to #5 send a valid token

#1{"doctorId": 7, "date": "2026-11-05", "notes": "Back pa      (body cut off)
    -> 500  Content-Type: text/html, an error page with a stack trace
#2  {"date": "2026-11-05"}
    -> 422  problem+json, errors: [{"pointer": "#/doctorId", "detail": "is required"}]
#3  {"doctorId": "7", "date": "2026-11-05"}
    -> 201  {"id": 5521, "doctorId": 7, "date": "2026-11-05"}
#4  {"doctorId": 7, "date": "2026-11-05", "notes": "<501 characters>"}
    -> 422  problem+json, {"status": 400, "title": "Bad Request", ...}
#5  DELETE /api/v1/appointments
    -> 404  problem+json, {"title": "Not Found"}
#6  no Authorization header, body {"date": "tomorrow"}
    -> 422  problem+json, errors for #/doctorId and #/date
الخطوات
  1. حدّد القاعدة التي يكسرها كل طلب، والرمز والجسم المتوقعين.
  2. قارن سطر الحالة الفعلي وContent-Type والجسم، بما في ذلك العنصر status.
  3. علّم كل اختبار بالنجاح أو الفشل، واكتب عنوان تقرير خطأ لكل فشل.
  4. استخرج القيم الحدّية الناقصة من العقد.
إظهار الإجابة النموذجية
الإجابة النموذجية

الاختبار ⁦#1⁩ فشل: JSON المقطوع صياغة تالفة، فالمتوقع 400 مع application/problem+json؛ أما صفحة HTML بالرمز 500 مع تتبّع المكدس فتسرّب تفاصيل داخلية. العنوان: «JSON مقطوع في POST /api/v1/appointments يعيد 500 مع تتبّع المكدس».

الاختبار ⁦#2⁩ نجح: 422 والصيغة الموثقة ومؤشر إلى الحقل الناقص. وتأكد أن شيئاً لم يُنشأ.

الاختبار ⁦#3⁩ فشل: "7" نص، فالمتوقع 422 مع المؤشر #/doctorId؛ لكن الواجهة حوّلته وأنشأت الموعد ⁦5521⁩، ويجب تنظيفه. العنوان: «قبول doctorId المرسل نصاً وإنشاء الموعد».

الاختبار ⁦#4⁩ فشل، وهو عيب ثانوي (Minor): الرمز 422 صحيح، لكن العنصر status يقول 400، وتشترط RFC 9457 تطابقهما. العنوان: «تفاصيل المشكلة لحقل notes الطويل تذكر الحالة 400 في استجابة 422».

الاختبار ⁦#5⁩ فشل: المسار يدعم GET وPOST، فالطريقة DELETE تستحق 405 مع Allow: GET, POST. العنوان: «DELETE /api/v1/appointments يعيد 404 بدلاً من 405».

الاختبار ⁦#6⁩ فشل: بيانات الاعتماد تُفحص أولاً، فالمتوقع 401 مع WWW-Authenticate: Bearer؛ أما الرمز 422 فيكشف قواعد التحقق لمستدعٍ مجهول. العنوان: «طلب موعد بلا مصادقة يتلقى أخطاء الحقول بدلاً من 401».

الحدود الناقصة: للحقل date القيم 2026-10-31 ← 422، و2026-11-01 ← 201، و2026-12-31 (بعد ⁦60⁩ يوماً) ← 201، و2027-01-01 ← 422؛ وللحقل notes ⁦500⁩ حرف ← 201، إضافة إلى "" وnull اللذين يجب أن يغطيهما العقد. وأيضاً doctorId بالقيمة ⁦0⁩ ← 422، وdoctorId غير موجود، ورمزه المتوقع (404 أو 422) سؤال للفريق.

أسئلة شائعة

هل تستحق القيمة غير الصالحة الرمز 400 أم 422؟

تعرّف RFC 9110 الرمز 400 لطلب يراه الخادم خطأً من العميل، والرمز 422 لمحتوى سليم الصياغة لا يمكن معالجته. وتستخدم واجهات كثيرة 400 للصياغة التالفة و422 للقيم غير الصالحة، كما في هذا الدرس، بينما تستخدم أخرى 400 للحالتين. وكلاهما مقبول إذا نص عليه العقد والتزمت به كل نقاط النهاية. أما 500 أو 200 فغير مقبولين أبداً.

هل يجب أن تستخدم كل واجهة API المواصفة RFC 9457؟

لا. RFC 9457 صيغة من مسار المعايير في IETF يمكن للواجهة أن تختار اعتمادها، وليست شرطاً من شروط HTTP. والصيغة القائمة مقبولة إذا كانت موثقة ومستخدمة باتساق. وميزة RFC 9457 أن الأدوات والمكتبات العامة تتعرّف على application/problem+json، فيحتاج العملاء إلى شيفرة خاصة أقل.

كم اختباراً سلبياً يحتاج الحقل الواحد؟

ابدأ باختبار واحد لكل فئة غير صالحة تنطبق على الحقل (غائب، null، نوع خاطئ، صيغة غير صالحة)، وبالقيم الحدّية حول كل حد. ثم رتّب الأولويات بحسب المخاطر: فالسعر أو الكمية يستحقان اختبارات أكثر من تعليق نصي حر. والأدوات التي تولّد الحالات من مستند OpenAPI تعطيك القائمة الكاملة بسرعة، وأنت من يقرر أيّ الصفوف تبقى.

هل إرسال طلبات غير صالحة إلى واجهة API آمن؟

فقط إلى أنظمة تملكها أو لديك إذن مكتوب باختبارها، ويُفضَّل في بيئة اختبار. وأفكار المدخلات في هذا الدرس غير مدمّرة، لكن الأجسام الكبيرة جداً أو الطلبات الكثيرة قد تؤثر في توفر الخدمة، فاتفق على الحدود مع الفريق أولاً.

المراجع

  • RFC 9110: HTTP SemanticsIETF · 2022معاني أساليب HTTP وحقول الترويسات ورموز الحالة والتفاوض على المحتوى، بغضّ النظر عن إصدار البروتوكول.
  • RFC 9457: Problem Details for HTTP APIsIETF · 2023صيغة الخطأ application/problem+json (type وtitle وstatus وdetail وinstance) لأخطاء API القابلة للقراءة آلياً.
  • OWASP API Security Top 10 (2023)OWASP Foundation · 2023أكثر عشر نقاط ضعف شيوعاً في واجهات API: خلل التفويض على مستوى الكائن، وخلل التوثيق، والاستهلاك غير المقيد للموارد، وغيرها.
  • JSON Schema SpecificationJSON Schemaالمفردات المستخدمة لوصف بنية مستندات JSON والتحقق منها (الأنواع والحقول المطلوبة والصيغ).

تدرّب بأدوات متقن

اختبر نقاط نهاية REST بتحكم كامل في الطريقة والترويسات وجسم JSON، ثم أضف تأكيدات على الحالة والترويسات ومسارات JSON.

اختبار واجهات API

ولّد حالات اختبار سلبية لحقول النماذج وواجهات API: قيم فارغة وnull ونوع خاطئ وطول زائد وحقن وصيغة غير صالحة.

تصميم الاختبارات

ولّد أجسام طلبات ومجموعات بيانات لاختبار واجهات API من مخطط أو قائمة حقول، بما في ذلك المتغيرات غير الصالحة.

بيانات الاختبار

تحقّق من استجابة API وفق التوقعات: الحالة والترويسات المطلوبة وJSON Schema وقواعد الحقول.

اختبار واجهات API

ولّد استجابات خطأ منظّمة (التحقق والمصادقة وعدم العثور وتحديد المعدّل وخطأ الخادم) بالصيغ الشائعة.

المحاكاة

للتعمّق أكثر

تقدّمك في هذا الموضوع

جزء من مسار تعلم ضمان الجودة