الاختبار السلبي لواجهات 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": 11 | 422نجاح | 201: الحد 10 يُفحص في واجهة المستخدم فقطفشل |
| نوع وسائط خاطئ | Content-Type: text/plain | 415نجاح | 500، أو صفحة خطأ HTMLفشل |
| طريقة خاطئة | PUT /api/v1/cart/items | 405مع Allow: GET, POSTنجاح | 404 أو 500 بلا ترويسة Allowفشل |
| مصادقة غير صالحة | بلا ترويسة Authorization | 401مع WWW-Authenticateنجاح | 422 مع أخطاء الحقول: قُرئ الجسم أولاًفشل |
| مورد غير موجود | GET /api/v1/cart/items/99999 | 404نجاح | 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" }
]
}مثال تطبيقي: اختبارات سلبية لإضافة عنصر إلى السلة
العقد: يتطلب 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أخطاء شائعة
- الاكتفاء بالتحقق من أن «الرمز من فئة 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- حدّد القاعدة التي يكسرها كل طلب، والرمز والجسم المتوقعين.
- قارن سطر الحالة الفعلي و
Content-Typeوالجسم، بما في ذلك العنصرstatus. - علّم كل اختبار بالنجاح أو الفشل، واكتب عنوان تقرير خطأ لكل فشل.
- استخرج القيم الحدّية الناقصة من العقد.
إظهار الإجابة النموذجية
الاختبار #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 Semanticsمعاني أساليب HTTP وحقول الترويسات ورموز الحالة والتفاوض على المحتوى، بغضّ النظر عن إصدار البروتوكول.
- RFC 9457: Problem Details for HTTP APIsصيغة الخطأ application/problem+json (type وtitle وstatus وdetail وinstance) لأخطاء API القابلة للقراءة آلياً.
- OWASP API Security Top 10 (2023)أكثر عشر نقاط ضعف شيوعاً في واجهات API: خلل التفويض على مستوى الكائن، وخلل التوثيق، والاستهلاك غير المقيد للموارد، وغيرها.
- JSON Schema Specificationالمفردات المستخدمة لوصف بنية مستندات JSON والتحقق منها (الأنواع والحقول المطلوبة والصيغ).
تدرّب بأدوات متقن
اختبر نقاط نهاية REST بتحكم كامل في الطريقة والترويسات وجسم JSON، ثم أضف تأكيدات على الحالة والترويسات ومسارات JSON.
ولّد حالات اختبار سلبية لحقول النماذج وواجهات API: قيم فارغة وnull ونوع خاطئ وطول زائد وحقن وصيغة غير صالحة.
ولّد أجسام طلبات ومجموعات بيانات لاختبار واجهات API من مخطط أو قائمة حقول، بما في ذلك المتغيرات غير الصالحة.
ولّد مصفوفة حالات اختبار منظّمة (إيجابية وسلبية ومصادقة وقيم حدّية) من مواصفة OpenAPI / Swagger.
تحقّق من استجابة API وفق التوقعات: الحالة والترويسات المطلوبة وJSON Schema وقواعد الحقول.
ولّد استجابات خطأ منظّمة (التحقق والمصادقة وعدم العثور وتحديد المعدّل وخطأ الخادم) بالصيغ الشائعة.
للتعمّق أكثر
تقدّمك في هذا الموضوع
سجّل الدخول لتتابع تقدّمك
علّم الموضوعات مكتملة، وتابع من حيث توقفت، وافتح امتحانات التخصص.