196 lines
5.4 KiB
Python
196 lines
5.4 KiB
Python
"""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
|