الخطوة 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".