Files
xiaozhi-esp32-server/main/manager-api-fastapi/tests/compatibility/external_mock.py
T

230 lines
7.5 KiB
Python

from __future__ import annotations
import asyncio
import base64
import hashlib
import json
from collections.abc import Mapping
from typing import Annotated, Any
from fastapi import FastAPI, File, Request, UploadFile
from fastapi.responses import JSONResponse
app = FastAPI(title="manager-api repeatable external-service mock")
_requests: list[dict[str, Any]] = []
_datasets: dict[str, dict[str, Any]] = {}
_documents: dict[str, dict[str, Any]] = {}
async def _capture(request: Request, body: Any = None) -> None:
_requests.append(
{
"method": request.method,
"path": request.url.path,
"query": dict(request.query_params),
"headers": {
key.lower(): value
for key, value in request.headers.items()
if key.lower() in {"authorization", "content-type"}
},
"body": body,
}
)
@app.get("/health")
async def health() -> dict[str, str]:
return {"status": "UP"}
@app.get("/__requests")
async def requests() -> dict[str, Any]:
return {"requests": list(_requests)}
@app.delete("/__requests")
async def reset_requests() -> dict[str, bool]:
_requests.clear()
return {"reset": True}
@app.post("/api/commands/{client_id}")
async def mqtt_command(client_id: str, request: Request) -> JSONResponse:
body = await request.json()
await _capture(request, body)
method = body.get("payload", {}).get("method") if isinstance(body, Mapping) else None
if method == "tools/list":
response = {
"success": True,
"data": {
"tools": [
{
"name": "contract.echo",
"description": "repeatable contract fixture",
"inputSchema": {"type": "object"},
}
]
},
}
elif method == "tools/call":
response = {"success": True, "data": {"content": [{"type": "text", "text": '{"echo":true}'}]}}
else:
response = {"success": True, "data": {"clientId": client_id}}
return JSONResponse(response)
@app.post("/api/devices/status")
async def mqtt_status(request: Request) -> JSONResponse:
body = await request.json()
await _capture(request, body)
client_ids = body.get("clientIds", []) if isinstance(body, Mapping) else []
return JSONResponse({"success": True, "data": {str(value): True for value in client_ids}})
@app.post("/api/call/{operation}")
async def mqtt_call(operation: str, request: Request) -> JSONResponse:
body = await request.json()
await _capture(request, body)
return JSONResponse({"status": "success", "message": operation})
@app.post("/v1/chat/completions")
async def chat_completions(request: Request) -> JSONResponse:
body = await request.json()
await _capture(request, body)
return JSONResponse(
{
"id": "chatcmpl-contract",
"choices": [{"index": 0, "message": {"role": "assistant", "content": "contract summary"}}],
}
)
@app.get("/api/v1/datasets")
async def list_datasets(request: Request) -> JSONResponse:
await _capture(request)
dataset_id = request.query_params.get("id")
values = list(_datasets.values())
if dataset_id:
values = [value for value in values if value["id"] == dataset_id]
return JSONResponse({"code": 0, "data": values})
@app.post("/api/v1/datasets")
async def create_dataset(request: Request) -> JSONResponse:
body = await request.json()
await _capture(request, body)
identifier = "rag-contract-" + hashlib.sha256(
json.dumps(body, ensure_ascii=False, sort_keys=True).encode()
).hexdigest()[:12]
value = {
"id": identifier,
"name": body.get("name"),
"description": body.get("description"),
"tenant_id": "tenant-contract",
"avatar": body.get("avatar"),
"document_count": 0,
"chunk_count": 0,
"token_num": 0,
}
_datasets[identifier] = value
return JSONResponse({"code": 0, "data": value})
@app.put("/api/v1/datasets/{dataset_id}")
async def update_dataset(dataset_id: str, request: Request) -> JSONResponse:
body = await request.json()
await _capture(request, body)
value = _datasets.setdefault(dataset_id, {"id": dataset_id})
value.update(body)
return JSONResponse({"code": 0, "data": value})
@app.delete("/api/v1/datasets/{dataset_id}")
async def delete_dataset(dataset_id: str, request: Request) -> JSONResponse:
await _capture(request)
_datasets.pop(dataset_id, None)
return JSONResponse({"code": 0, "data": True})
@app.post("/api/v1/datasets/{dataset_id}/documents")
async def upload_document(
dataset_id: str,
request: Request,
file: Annotated[UploadFile, File()],
) -> JSONResponse:
content = await file.read()
body: dict[str, Any] = {
"filename": file.filename,
"size": len(content),
"sha256": hashlib.sha256(content).hexdigest(),
}
await _capture(request, body)
identifier = "doc-contract-" + str(body["sha256"])[:12]
value = {
"id": identifier,
"dataset_id": dataset_id,
"name": file.filename,
"size": len(content),
"run": "UNSTART",
"progress": 0.0,
"chunk_count": 0,
"token_count": 0,
}
_documents[identifier] = value
return JSONResponse({"code": 0, "data": [value]})
@app.get("/api/v1/datasets/{dataset_id}/documents")
async def list_documents(dataset_id: str, request: Request) -> JSONResponse:
await _capture(request)
document_id = request.query_params.get("id")
values = [value for value in _documents.values() if value["dataset_id"] == dataset_id]
if document_id:
values = [value for value in values if value["id"] == document_id]
return JSONResponse({"code": 0, "data": {"docs": values, "total": len(values)}})
@app.post("/api/v1/datasets/{dataset_id}/chunks")
async def parse_documents(dataset_id: str, request: Request) -> JSONResponse:
body = await request.json()
await _capture(request, body)
for document_id in body.get("document_ids", []):
if document_id in _documents:
_documents[document_id].update(run="DONE", progress=1.0, chunk_count=2, token_count=17)
return JSONResponse({"code": 0, "data": True})
@app.delete("/api/v1/datasets/{dataset_id}/documents")
async def delete_documents(dataset_id: str, request: Request) -> JSONResponse:
body = await request.json()
await _capture(request, body)
for document_id in body.get("ids", body.get("document_ids", [])):
_documents.pop(document_id, None)
return JSONResponse({"code": 0, "data": True})
@app.post("/voice-clone")
async def clone_voice(request: Request) -> JSONResponse:
content = await request.body()
await _capture(request, {"size": len(content), "sha256": hashlib.sha256(content).hexdigest()})
return JSONResponse({"voice_id": "voice-contract", "status": "success"})
@app.post("/transient/{failures}")
async def transient(failures: int, request: Request) -> JSONResponse:
await _capture(request)
count = sum(1 for item in _requests if item["path"] == request.url.path)
if count <= failures:
await asyncio.sleep(0)
return JSONResponse({"error": "retryable"}, status_code=503)
return JSONResponse({"ok": True})
@app.get("/binary")
async def binary(request: Request) -> JSONResponse:
await _capture(request)
content = b"contract-binary\x00\xff"
return JSONResponse({"base64": base64.b64encode(content).decode(), "size": len(content)})