refactor: enforce 800 line source limits
This commit is contained in:
34
server/tests/test_code_size_limits.py
Normal file
34
server/tests/test_code_size_limits.py
Normal file
@@ -0,0 +1,34 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import ast
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
MAX_CLASS_LINES = 800
|
||||
SERVER_SOURCE_ROOT = Path(__file__).resolve().parents[1] / "src" / "app"
|
||||
|
||||
|
||||
def iter_python_source_files(root: Path) -> list[Path]:
|
||||
ignored_parts = {"__pycache__", "x_financial_server.egg-info"}
|
||||
return sorted(
|
||||
path
|
||||
for path in root.rglob("*.py")
|
||||
if not ignored_parts.intersection(path.parts)
|
||||
)
|
||||
|
||||
|
||||
def test_python_classes_do_not_exceed_800_lines() -> None:
|
||||
oversized_classes: list[str] = []
|
||||
|
||||
for path in iter_python_source_files(SERVER_SOURCE_ROOT):
|
||||
tree = ast.parse(path.read_text(encoding="utf-8"))
|
||||
for node in ast.walk(tree):
|
||||
if isinstance(node, ast.ClassDef) and node.end_lineno is not None:
|
||||
line_count = node.end_lineno - node.lineno + 1
|
||||
if line_count > MAX_CLASS_LINES:
|
||||
relative_path = path.relative_to(SERVER_SOURCE_ROOT.parents[1])
|
||||
oversized_classes.append(
|
||||
f"{relative_path}:{node.lineno} {node.name} ({line_count} lines)"
|
||||
)
|
||||
|
||||
assert oversized_classes == []
|
||||
@@ -2207,6 +2207,160 @@ def test_upload_train_ticket_attachment_backfills_item_amount(monkeypatch, tmp_p
|
||||
assert not any("用途字段" in point for point in uploaded_meta["analysis"]["points"])
|
||||
|
||||
|
||||
def test_upload_auto_collected_attachment_uses_source_receipt_ocr_result(
|
||||
monkeypatch,
|
||||
tmp_path,
|
||||
) -> None:
|
||||
monkeypatch.setenv("STORAGE_ROOT_DIR", str(tmp_path / "storage"))
|
||||
get_settings.cache_clear()
|
||||
try:
|
||||
current_user = CurrentUserContext(
|
||||
username="auto-collect-travel@example.com",
|
||||
name="张三",
|
||||
role_codes=[],
|
||||
is_admin=False,
|
||||
)
|
||||
|
||||
def fake_recognize(
|
||||
self,
|
||||
files: list[tuple[str, bytes, str | None]],
|
||||
) -> OcrRecognizeBatchRead:
|
||||
return OcrRecognizeBatchRead(
|
||||
total_file_count=1,
|
||||
success_count=1,
|
||||
documents=[
|
||||
OcrRecognizeDocumentRead(
|
||||
filename="2月22 深圳-上海.pdf",
|
||||
media_type="application/pdf",
|
||||
text="",
|
||||
summary="",
|
||||
avg_score=0.0,
|
||||
line_count=0,
|
||||
page_count=1,
|
||||
document_type="other",
|
||||
document_type_label="其他单据",
|
||||
scene_code="other",
|
||||
scene_label="其他票据",
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
monkeypatch.setattr(OcrService, "recognize_files", fake_recognize)
|
||||
monkeypatch.setattr(
|
||||
ExpenseClaimAttachmentStorage,
|
||||
"root",
|
||||
lambda self: tmp_path / "attachments",
|
||||
)
|
||||
|
||||
with build_session() as db:
|
||||
employee = Employee(
|
||||
employee_no="E-AUTO-COLLECT",
|
||||
name="张三",
|
||||
email=current_user.username,
|
||||
grade="P4",
|
||||
)
|
||||
db.add(employee)
|
||||
db.flush()
|
||||
|
||||
claim = build_claim(expense_type="travel", location="上海")
|
||||
claim.employee = employee
|
||||
claim.employee_id = employee.id
|
||||
claim.employee_name = employee.name
|
||||
claim.amount = Decimal("0.00")
|
||||
claim.invoice_count = 0
|
||||
claim.risk_flags_json = [
|
||||
{
|
||||
"source": "attachment_analysis",
|
||||
"severity": "high",
|
||||
"message": "票据类型:未识别到发票、票据、电子行程单等关键字。",
|
||||
}
|
||||
]
|
||||
claim.items[0].item_type = "travel"
|
||||
claim.items[0].item_reason = ""
|
||||
claim.items[0].item_location = "上海"
|
||||
claim.items[0].item_amount = Decimal("0.00")
|
||||
claim.items[0].invoice_id = None
|
||||
db.add(claim)
|
||||
db.commit()
|
||||
|
||||
receipt = ReceiptFolderService().save_receipt(
|
||||
filename="2月22 深圳-上海.pdf",
|
||||
content=b"%PDF-1.4 fake-train-ticket",
|
||||
media_type="application/pdf",
|
||||
current_user=current_user,
|
||||
document=OcrRecognizeDocumentRead(
|
||||
filename="2月22 深圳-上海.pdf",
|
||||
media_type="application/pdf",
|
||||
text="中国铁路电子客票 深圳北-上海虹桥 2026-02-22 票价:¥388.00",
|
||||
summary="铁路电子客票,深圳至上海,2026-02-22 出发,票价 388 元。",
|
||||
avg_score=0.98,
|
||||
line_count=1,
|
||||
page_count=1,
|
||||
document_type="train_ticket",
|
||||
document_type_label="火车/高铁票",
|
||||
scene_code="travel",
|
||||
scene_label="差旅票据",
|
||||
document_fields=[
|
||||
{"key": "origin", "label": "出发城市", "value": "深圳"},
|
||||
{"key": "destination", "label": "到达城市", "value": "上海"},
|
||||
{"key": "trip_date", "label": "列车出发时间", "value": "2026-02-22"},
|
||||
{"key": "fare", "label": "票价", "value": "¥388.00"},
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
service = ExpenseClaimService(db)
|
||||
updated = service.upload_claim_item_attachment(
|
||||
claim_id=claim.id,
|
||||
item_id=claim.items[0].id,
|
||||
filename="2月22 深圳-上海.pdf",
|
||||
content=b"%PDF-1.4 fake-train-ticket",
|
||||
media_type="application/pdf",
|
||||
current_user=current_user,
|
||||
source_receipt_id=receipt.id,
|
||||
)
|
||||
|
||||
assert updated is not None
|
||||
assert updated["item_type"] == "train_ticket"
|
||||
assert updated["item_amount"] == Decimal("388.00")
|
||||
assert updated["item_date"] == "2026-02-22"
|
||||
assert updated["item_reason"] == "深圳北-上海虹桥"
|
||||
|
||||
uploaded_meta = service.get_claim_item_attachment_meta(
|
||||
claim_id=claim.id,
|
||||
item_id=claim.items[0].id,
|
||||
current_user=current_user,
|
||||
)
|
||||
assert uploaded_meta is not None
|
||||
assert uploaded_meta["document_info"]["document_type"] == "train_ticket"
|
||||
assert uploaded_meta["requirement_check"]["matches"] is True
|
||||
assert not any(
|
||||
"未识别到发票" in point or "当前识别为其他单据" in point
|
||||
for point in uploaded_meta["analysis"]["points"]
|
||||
)
|
||||
|
||||
db.refresh(claim)
|
||||
allowance_item = next(
|
||||
item for item in claim.items if item.item_type == "travel_allowance"
|
||||
)
|
||||
assert allowance_item.item_amount > Decimal("0.00")
|
||||
assert "1天" in allowance_item.item_reason
|
||||
assert claim.amount == Decimal("388.00") + allowance_item.item_amount
|
||||
assert not any(
|
||||
isinstance(flag, dict)
|
||||
and str(flag.get("source") or "").strip() == "attachment_analysis"
|
||||
and "未识别到发票" in str(flag.get("message") or "")
|
||||
for flag in list(claim.risk_flags_json or [])
|
||||
)
|
||||
|
||||
linked_receipt = ReceiptFolderService().get_receipt(receipt.id, current_user)
|
||||
assert linked_receipt.status == "linked"
|
||||
assert linked_receipt.linked_claim_id == claim.id
|
||||
assert linked_receipt.linked_claim_no == claim.claim_no
|
||||
finally:
|
||||
get_settings.cache_clear()
|
||||
|
||||
|
||||
def test_upload_attachment_response_includes_refreshed_rule_center_risk_flags(
|
||||
monkeypatch,
|
||||
tmp_path,
|
||||
|
||||
Reference in New Issue
Block a user