الخطوة 4 في الواجهة الخلفية: نقاط نهاية API
ستنشئ هنا نقاط نهاية API الفعلية للدردشة، وهي التي سيستدعيها تطبيق الجوال.
ما ستتعلمه
- نقاط نهاية POST وGET
- قراءة بيانات الطلب باستخدام
request.get_json() - حفظ البيانات واسترجاعها من قاعدة البيانات
- استخدام Blueprints لتنظيم المسارات
الخطوة 1: إنشاء Blueprint
يتيح لك Blueprint تعريف المسارات في ملف منفصل. أنشئ الملف backend/app/routes.py:
from flask import Blueprint, request, jsonify
from . import db
from .models import Message
api = Blueprint("api", __name__)
@api.route("/status")
def status():
return jsonify({"status": "ok"})
@api.route("/messages", methods=["GET"])
def get_messages():
messages = Message.query.order_by(Message.timestamp.asc()).all()
return jsonify([msg.to_dict() for msg in messages])
@api.route("/chat", methods=["POST"])
def chat():
data = request.get_json()
user_message = data.get("message", "")
if not user_message:
return jsonify({"error": "Message is required"}), 400
user_msg = Message(content=user_message, sender="user")
db.session.add(user_msg)
db.session.commit()
# في الوقت الحالي، الرد ثابت ومكتوب في الكود
ai_reply = f"You said: '{user_message}'. I'm a basic bot for now!"
ai_msg = Message(content=ai_reply, sender="ai")
db.session.add(ai_msg)
db.session.commit()
return jsonify({
"user_message": user_msg.to_dict(),
"ai_message": ai_msg.to_dict(),
})
الخطوة 2: تنظيم الاختبارات باستخدام conftest.py
عندما يكون لديك عدة ملفات اختبار، فإنها غالبًا ما تشترك في شيفرة الإعداد ذاتها. بدلًا من تكرارها، استخدم ملف conftest.py يحمّله pytest تلقائيًا.
أنشئ الملف backend/tests/conftest.py:
import pytest
from app import create_app, db as _db
@pytest.fixture
def app():
app = create_app()
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///:memory:"
app.config["TESTING"] = True
return app
@pytest.fixture
def client(app):
return app.test_client()
@pytest.fixture(autouse=True)
def db(app):
with app.app_context():
_db.create_all()
yield _db
_db.session.rollback()
_db.drop_all()
هذا الملف:
- ينشئ تطبيق Flask في الذاكرة (بلا ملفات، وبلا بيانات متبقية)
- يوفر تجهيزة (fixture) باسم client تستخدمها اختباراتك
- يهيئ قاعدة البيانات ثم يزيلها قبل كل اختبار وبعده
- تعني autouse=True أنه يعمل تلقائيًا، فلا حاجة إلى ذكره
الآن يمكن لأي ملف اختبار في tests/ أن يستخدم client وapp دون أي شيفرة إعداد.
الخطوة 3: تحديث init.py لاستخدام Blueprint
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from dotenv import load_dotenv
load_dotenv()
db = SQLAlchemy()
def create_app():
app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///chat.db"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
db.init_app(app)
from .routes import api
app.register_blueprint(api, url_prefix="/api")
with app.app_context():
db.create_all()
return app
if __name__ == "__main__":
app = create_app()
app.run(debug=True, port=5000)
الخطوة 4: كتابة اختبارات آلية لنقاط النهاية
أنشئ الملف backend/tests/test_chat.py:
def test_chat_with_message_returns_two_messages(client):
response = client.post("/api/chat", json={"message": "Hello"})
assert response.status_code == 200
data = response.get_json()
assert "user_message" in data
assert "ai_message" in data
assert data["user_message"]["sender"] == "user"
assert data["ai_message"]["sender"] == "ai"
def test_chat_empty_message_returns_400(client):
response = client.post("/api/chat", json={})
assert response.status_code == 400
assert "error" in response.get_json()
أنشئ الملف backend/tests/test_messages.py:
def test_get_messages_returns_list(client):
response = client.get("/api/messages")
assert response.status_code == 200
assert isinstance(response.get_json(), list)
def test_delete_nonexistent_message_returns_404(client):
response = client.delete("/api/messages/999")
assert response.status_code == 404
def test_delete_message_works(client):
client.post("/api/chat", json={"message": "Test"})
messages = client.get("/api/messages").get_json()
msg_id = messages[0]["id"]
response = client.delete(f"/api/messages/{msg_id}")
assert response.status_code == 200
assert response.get_json()["deleted"] is True
شغّل جميع الاختبارات:
cd aiproject/backend
python -m pytest tests/ -v
المخرجات المتوقعة (5 اختبارات):
tests/test_chat.py::test_chat_with_message_returns_two_messages PASSED
tests/test_chat.py::test_chat_empty_message_returns_400 PASSED
tests/test_messages.py::test_get_messages_returns_list PASSED
tests/test_messages.py::test_delete_nonexistent_message_returns_404 PASSED
tests/test_messages.py::test_delete_message_works PASSED
ما تغطيه هذه الاختبارات: - المسار الطبيعي (happy path): إرسال رسالة يعمل بشكل صحيح - الحالة الطرفية: رسالة فارغة تعيد خطأ - نقطة نهاية GET: تعيد قائمة - DELETE: حالتا النجاح وعدم العثور على الرسالة
يسمى هذا اختبار الحدود (boundary testing)، أي اختبار المسار الطبيعي وحالات الخطأ معًا.
الخطوة 5: اختبار نقاط النهاية (يدويًا)
اختبار GET /api/status
curl http://127.0.0.1:5000/api/status
# → {"status":"ok"}
اختبار POST /api/chat
curl -X POST http://127.0.0.1:5000/api/chat \
-H "Content-Type: application/json" \
-d '{"message": "Hello!"}'
الاستجابة المتوقعة:
{
"user_message": {"id": 1, "content": "Hello!", "sender": "user", "timestamp": "..."},
"ai_message": {"id": 2, "content": "You said: 'Hello!'. I'm a basic bot for now!", "sender": "ai", "timestamp": "..."}
}
اختبار GET /api/messages
curl http://127.0.0.1:5000/api/messages
الاستجابة المتوقعة: قائمة بجميع الرسائل حتى الآن.
اختبار POST برسالة فارغة
curl -X POST http://127.0.0.1:5000/api/chat \
-H "Content-Type: application/json" \
-d '{}'
المتوقع: {"error":"Message is required"} مع رمز الحالة 400.
الخطوة 6: كيف يعمل
تسلسل POST /api/chat:
1. يرسل تطبيق الجوال JSON بالشكل {"message": "Hello!"}
2. يستقبل Flask الطلب
3. تحلل request.get_json() جسم JSON
4. ينشئ كائن Message جديد بقيمة sender="user" ويحفظه في قاعدة البيانات
5. يولّد ردًّا مؤقتًا من الذكاء الاصطناعي
6. يحفظ رد الذكاء الاصطناعي في قاعدة البيانات
7. يعيد الرسالتين بصيغة JSON
تسلسل GET /api/messages:
1. يطلب تطبيق الجوال جميع الرسائل
2. يستعلم Flask من قاعدة البيانات مع ترتيبها حسب الطابع الزمني
3. يحول كل كائن Message إلى قاموس (dict) عبر to_dict()
4. يعيد القائمة بصيغة JSON
الخطوة 7: فهم طرق HTTP
| الطريقة | نقطة النهاية | ما تفعله |
|---|---|---|
| GET | /api/messages |
قراءة جميع الرسائل |
| POST | /api/chat |
إرسال رسالة والحصول على رد |
تستخدم POST عند إنشاء البيانات، وتستخدم GET عند قراءتها.
الخطوة 8: التحقق
شغّل الاختبارات الثلاثة أعلاه وتأكد من حصولك على استجابات JSON. وإذا أعاد POST خطأ بالرمز 400، فتحقق من أنك أضفت الترويسة -H "Content-Type: application/json".