Refactor chat backend integration and introduce new backend-neutral architecture
- Replaced direct FastGPT client usage with a backend-neutral chat interface, allowing for improved flexibility and maintainability. - Introduced a new `ChatBackend` protocol and implemented `FastGPTBackend` to handle chat operations. - Updated the chat endpoint to utilize the new backend structure, enhancing the handling of chat requests and responses. - Added comprehensive tests to ensure compatibility and functionality of the new backend integration.
This commit is contained in:
195
test/api/test_public_schema_contract.py
Normal file
195
test/api/test_public_schema_contract.py
Normal file
@@ -0,0 +1,195 @@
|
||||
"""Characterization tests for the public HTTP API schemas.
|
||||
|
||||
These tests freeze the current contract before the FastGPT backend is replaced.
|
||||
They should change only when the teams integrating with this service agree to a
|
||||
contract change.
|
||||
"""
|
||||
|
||||
from collections.abc import Callable
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
from pydantic import BaseModel, ValidationError
|
||||
|
||||
from src.schemas.models import (
|
||||
ProcessRequest_chat,
|
||||
ProcessRequest_get,
|
||||
ProcessRequest_set,
|
||||
ProcessResponse_chat,
|
||||
ProcessResponse_get,
|
||||
ProcessResponse_set,
|
||||
)
|
||||
|
||||
|
||||
SESSION_ID = "session-001"
|
||||
TIMESTAMP = "20260725120000"
|
||||
|
||||
|
||||
def chat_request_payload() -> dict[str, Any]:
|
||||
return {
|
||||
"sessionId": SESSION_ID,
|
||||
"timeStamp": TIMESTAMP,
|
||||
"text": "发生了交通事故",
|
||||
}
|
||||
|
||||
|
||||
def chat_response_payload() -> dict[str, Any]:
|
||||
return {
|
||||
"sessionId": SESSION_ID,
|
||||
"timeStamp": TIMESTAMP,
|
||||
"outputText": "请描述事故经过。",
|
||||
"nextStage": "通话中",
|
||||
"nextStageCode": "1002",
|
||||
"code": "200",
|
||||
}
|
||||
|
||||
|
||||
def get_request_payload() -> dict[str, Any]:
|
||||
return {
|
||||
"sessionId": SESSION_ID,
|
||||
"timeStamp": TIMESTAMP,
|
||||
"key": "acdinfo",
|
||||
}
|
||||
|
||||
|
||||
def get_response_payload() -> dict[str, Any]:
|
||||
return {
|
||||
"sessionId": SESSION_ID,
|
||||
"timeStamp": TIMESTAMP,
|
||||
"value": '{"jdcsl": "2"}',
|
||||
"code": "200",
|
||||
}
|
||||
|
||||
|
||||
def set_request_payload() -> dict[str, Any]:
|
||||
return {
|
||||
"sessionId": SESSION_ID,
|
||||
"timeStamp": TIMESTAMP,
|
||||
"key": "hphm1",
|
||||
"value": "沪A12345",
|
||||
}
|
||||
|
||||
|
||||
def set_response_payload() -> dict[str, Any]:
|
||||
return {
|
||||
"sessionId": SESSION_ID,
|
||||
"timeStamp": TIMESTAMP,
|
||||
"code": "200",
|
||||
}
|
||||
|
||||
|
||||
def test_chat_request_defaults_are_backward_compatible() -> None:
|
||||
request = ProcessRequest_chat(**chat_request_payload())
|
||||
|
||||
assert request.model_dump() == {
|
||||
**chat_request_payload(),
|
||||
"needFormUpdate": False,
|
||||
"useTextChunk": False,
|
||||
}
|
||||
|
||||
|
||||
def test_chat_response_shape_is_backward_compatible() -> None:
|
||||
response = ProcessResponse_chat(**chat_response_payload())
|
||||
|
||||
assert response.model_dump() == {
|
||||
**chat_response_payload(),
|
||||
"formUpdate": {},
|
||||
"msg": None,
|
||||
}
|
||||
|
||||
|
||||
def test_get_info_shapes_are_backward_compatible() -> None:
|
||||
request = ProcessRequest_get(**get_request_payload())
|
||||
response = ProcessResponse_get(**get_response_payload())
|
||||
|
||||
assert request.model_dump() == {
|
||||
**get_request_payload(),
|
||||
"includeInputInfo": False,
|
||||
}
|
||||
assert response.model_dump() == {
|
||||
**get_response_payload(),
|
||||
"msg": None,
|
||||
}
|
||||
|
||||
|
||||
def test_set_info_shapes_are_backward_compatible() -> None:
|
||||
request = ProcessRequest_set(**set_request_payload())
|
||||
response = ProcessResponse_set(**set_response_payload())
|
||||
|
||||
assert request.model_dump() == {
|
||||
**set_request_payload(),
|
||||
"includeInputInfo": False,
|
||||
}
|
||||
assert response.model_dump() == {
|
||||
**set_response_payload(),
|
||||
"msg": None,
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("model", "payload_factory", "required_field"),
|
||||
[
|
||||
(ProcessRequest_chat, chat_request_payload, "sessionId"),
|
||||
(ProcessRequest_chat, chat_request_payload, "timeStamp"),
|
||||
(ProcessRequest_chat, chat_request_payload, "text"),
|
||||
(ProcessRequest_get, get_request_payload, "key"),
|
||||
(ProcessRequest_set, set_request_payload, "key"),
|
||||
(ProcessRequest_set, set_request_payload, "value"),
|
||||
(ProcessResponse_chat, chat_response_payload, "nextStageCode"),
|
||||
(ProcessResponse_get, get_response_payload, "value"),
|
||||
],
|
||||
)
|
||||
def test_required_fields_remain_required(
|
||||
model: type[BaseModel],
|
||||
payload_factory: Callable[[], dict[str, Any]],
|
||||
required_field: str,
|
||||
) -> None:
|
||||
payload = payload_factory()
|
||||
payload.pop(required_field)
|
||||
|
||||
with pytest.raises(ValidationError):
|
||||
model(**payload)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("model", "payload_factory", "field_name", "invalid_value"),
|
||||
[
|
||||
(ProcessRequest_chat, chat_request_payload, "sessionId", "s" * 65),
|
||||
(ProcessRequest_chat, chat_request_payload, "timeStamp", "t" * 33),
|
||||
(ProcessRequest_get, get_request_payload, "sessionId", "s" * 65),
|
||||
(ProcessRequest_set, set_request_payload, "timeStamp", "t" * 33),
|
||||
(ProcessResponse_chat, chat_response_payload, "nextStage", "n" * 33),
|
||||
(ProcessResponse_chat, chat_response_payload, "nextStageCode", "10020"),
|
||||
(ProcessResponse_chat, chat_response_payload, "code", "10000"),
|
||||
(ProcessResponse_get, get_response_payload, "code", "10000"),
|
||||
(ProcessResponse_set, set_response_payload, "code", "10000"),
|
||||
],
|
||||
)
|
||||
def test_public_length_limits_are_enforced(
|
||||
model: type[BaseModel],
|
||||
payload_factory: Callable[[], dict[str, Any]],
|
||||
field_name: str,
|
||||
invalid_value: str,
|
||||
) -> None:
|
||||
payload = payload_factory()
|
||||
payload[field_name] = invalid_value
|
||||
|
||||
with pytest.raises(ValidationError):
|
||||
model(**payload)
|
||||
|
||||
|
||||
def test_get_info_value_remains_a_json_encoded_string() -> None:
|
||||
response = ProcessResponse_get(**get_response_payload())
|
||||
|
||||
assert isinstance(response.value, str)
|
||||
assert response.value == '{"jdcsl": "2"}'
|
||||
|
||||
|
||||
def test_chat_form_update_remains_unstructured_for_compatibility() -> None:
|
||||
form_update = {"jdcsl": 2, "ywrysw": False}
|
||||
response = ProcessResponse_chat(
|
||||
**chat_response_payload(),
|
||||
formUpdate=form_update,
|
||||
)
|
||||
|
||||
assert response.formUpdate == form_update
|
||||
Reference in New Issue
Block a user