Add UTF-8 safe Chinese template validation

This commit is contained in:
2026-05-27 10:44:17 +08:00
parent 8d1615e1ed
commit 029fbfb5f6
3 changed files with 216 additions and 4 deletions
+122 -4
View File
@@ -1,3 +1,4 @@
import json
import unittest
from datetime import datetime, timedelta, timezone
@@ -308,6 +309,29 @@ class StubContractGenerationService:
def preview_contract(self, current_user, template_id, variable_values):
if template_id == "tpl-missing":
raise HTTPException(status_code=404, detail="template not found")
if "甲方名称" in variable_values:
return {
"preview_content": f"甲方:{variable_values.get('甲方名称', '')}\n乙方:{variable_values.get('乙方名称', '')}",
"template_name": "Property Service Template",
"variable_summary": [
{
"name": "甲方名称",
"label": "甲方名称",
"required": True,
"provided": "甲方名称" in variable_values,
"value_preview": variable_values.get("甲方名称", ""),
"is_empty": not bool(variable_values.get("甲方名称", "").strip()),
},
{
"name": "乙方名称",
"label": "乙方名称",
"required": True,
"provided": "乙方名称" in variable_values,
"value_preview": variable_values.get("乙方名称", ""),
"is_empty": not bool(variable_values.get("乙方名称", "").strip()),
},
],
}
return {
"preview_content": f"Contract with {variable_values.get('supplier_name', '')}",
"template_name": "Procurement Template",
@@ -441,13 +465,23 @@ class StubContractGenerationService:
):
if template_id == "tpl-missing":
raise HTTPException(status_code=404, detail="template not found")
if "甲方名称" in variable_values:
content = f"甲方:{variable_values.get('甲方名称', '')}\n乙方:{variable_values.get('乙方名称', '')}"
contract_type = "property_service"
document_type_code = document_type_code or "property_service"
template_name = "Property Service Template"
else:
content = f"Contract with {variable_values.get('supplier_name', '')}"
contract_type = "procurement"
document_type_code = document_type_code or "procurement_goods"
template_name = "Procurement Template"
return {
"id": "contract-123",
"template_id": template_id,
"template_name": "Procurement Template",
"contract_type": "procurement",
"document_type_code": document_type_code or "procurement_goods",
"generated_content": f"Contract with {variable_values.get('supplier_name', '')}",
"template_name": template_name,
"contract_type": contract_type,
"document_type_code": document_type_code,
"generated_content": content,
"generated_html": "<article>Contract</article>",
"variable_values": variable_values,
"elements_snapshot": elements_snapshot,
@@ -740,6 +774,25 @@ class ContractsApiTestCase(unittest.TestCase):
self.assertEqual([call[0] for call in self.tenant_service.calls], ["generation", "review"])
self.assertEqual(self.audit_log_service.operations[0]["action"], "contract_generation")
def test_user_can_generate_contract_with_chinese_variable_keys(self):
self._login()
payload = {
"甲方名称": "甲方物业公司",
"乙方名称": "乙方服务公司",
}
response = self.client.post(
"/api/v1/contracts/generate",
data={
"template_id": "tpl-123",
"variable_values_json": json.dumps(payload, ensure_ascii=False),
"export_format": "docx",
},
)
self.assertEqual(response.status_code, 200)
body = response.json()
self.assertEqual(body["data"]["variable_values"]["甲方名称"], "甲方物业公司")
self.assertEqual(body["data"]["variable_values"]["乙方名称"], "乙方服务公司")
def test_user_can_generate_contract_from_elements_with_auto_template(self):
self._login()
response = self.client.post(
@@ -779,6 +832,22 @@ class ContractsApiTestCase(unittest.TestCase):
self.assertEqual(preview.status_code, 200)
self.assertIn("Supplier A", preview.json()["data"]["preview_content"])
chinese_preview = self.client.post(
"/api/v1/contracts/preview",
data={
"template_id": "tpl-123",
"variable_values_json": json.dumps(
{"甲方名称": "甲方物业公司", "乙方名称": "乙方服务公司"},
ensure_ascii=False,
),
},
)
self.assertEqual(chinese_preview.status_code, 200)
self.assertEqual(
chinese_preview.json()["data"]["variable_summary"][0]["name"],
"甲方名称",
)
download = self.client.get("/api/v1/contracts/contract-123/download")
self.assertEqual(download.status_code, 200)
self.assertEqual(download.content, b"pdf-bytes")
@@ -1168,6 +1237,55 @@ class ContractGenerationServiceTestCase(unittest.TestCase):
"contract_generation.template_render.v1",
)
def test_property_service_chinese_variable_template_supports_preview_and_generate(self):
now = datetime.now(timezone.utc)
template = self.ContractTemplate(
id="tpl-property-cn",
tenant_id="tenant-demo-001",
name="物业服务合同模板",
contract_type="property_service",
document_type_code="property_service",
description="Chinese variable template",
content="甲方:{{甲方名称}}\n乙方:{{乙方名称}}\n项目:{{项目/物业名称}}",
variables_json=[
{"name": "甲方名称", "label": "甲方名称", "required": True},
{"name": "乙方名称", "label": "乙方名称", "required": True},
{"name": "项目/物业名称", "label": "项目/物业名称", "required": True},
],
enabled=True,
created_by="seed",
updated_by="seed",
created_at=now,
updated_at=now,
)
self.db.add(template)
self.db.commit()
variable_values = {
"甲方名称": "甲方物业公司",
"乙方名称": "乙方服务公司",
"项目/物业名称": "测试小区",
}
preview = self.service.preview_contract(
current_user=self.current_user,
template_id="tpl-property-cn",
variable_values=variable_values,
)
self.assertIn("甲方物业公司", preview["preview_content"])
self.assertEqual(preview["variable_summary"][0]["name"], "甲方名称")
self.assertTrue(preview["variable_summary"][0]["provided"])
result = self.service.generate_contract(
current_user=self.current_user,
template_id="tpl-property-cn",
variable_values=variable_values,
)
self.assertEqual(result["template_id"], "tpl-property-cn")
self.assertEqual(result["document_type_code"], "property_service")
self.assertEqual(result["variable_values"]["甲方名称"], "甲方物业公司")
self.assertIn("测试小区", result["generated_content"])
def test_generate_contract_from_elements_auto_selects_template_and_returns_law_doc_004_payload(self):
self._seed_law_doc_004_data()
result = self.service.generate_contract_from_elements(
@@ -0,0 +1,15 @@
param(
[string]$ApiBaseUrl = "http://192.168.3.90:8001/api/v1",
[string]$Account = "cyuqei@gmail.com",
[string]$Password = "816301",
[string]$TemplateId = "tpl-006aea61de80"
)
$ErrorActionPreference = "Stop"
$scriptPath = Join-Path $PSScriptRoot "verify_chinese_contract_template_utf8.py"
if (-not (Test-Path $scriptPath)) {
throw "Python verifier not found at $scriptPath"
}
python $scriptPath $ApiBaseUrl $Account $Password $TemplateId
@@ -0,0 +1,79 @@
import json
import sys
import requests
def main() -> int:
api_base = sys.argv[1].rstrip("/")
account = sys.argv[2]
password = sys.argv[3]
template_id = sys.argv[4]
payload = {
"甲方名称": "甲方物业公司",
"乙方名称": "乙方服务公司",
"项目/物业名称": "测试小区",
"物业坐落": "北京市朝阳区测试路1号",
"物业类型": "住宅",
"服务区域/范围": "全区物业服务",
"服务内容和标准": "保洁安保维修",
"服务期限": "2026年1月1日至2026年12月31日",
"物业服务费标准": "每月10万元",
"费用计收方式": "按月支付",
"付款周期/缴费期限": "每月5日前支付",
"共用部位/共用设施设备维护": "乙方负责日常维护",
"维修资金/专项维修安排": "按维修基金制度执行",
"公共秩序和安全协助事项": "安保巡逻与秩序维护",
"环境卫生与绿化维护": "保洁与绿化养护",
"业主/使用人义务": "遵守物业管理制度",
"业主委员会/授权依据": "业委会授权决议",
"交接验收与资料移交": "完工交接清单",
"违约责任": "违约方承担违约责任",
"争议解决方式": "北京仲裁委员会仲裁",
"签署日期": "2026-05-27",
}
login = requests.post(
f"{api_base}/auth/login",
json={"account": account, "password": password},
timeout=30,
)
login.raise_for_status()
token = login.json()["data"]["access_token"]
headers = {"Authorization": f"Bearer {token}"}
form = {
"template_id": template_id,
"variable_values_json": json.dumps(payload, ensure_ascii=False),
"export_format": "txt",
}
preview = requests.post(
f"{api_base}/contracts/preview",
headers={**headers, "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"},
data=form,
timeout=60,
)
preview.raise_for_status()
generate = requests.post(
f"{api_base}/contracts/generate",
headers={**headers, "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"},
data=form,
timeout=180,
)
generate.raise_for_status()
result = {
"preview_status": preview.status_code,
"preview_body": preview.json(),
"generate_status": generate.status_code,
"generate_body": generate.json(),
}
print(json.dumps(result, ensure_ascii=False, indent=2))
return 0
if __name__ == "__main__":
raise SystemExit(main())